From 4cc39129854bc545c6c92aaca69d995e4e666600 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 25 Aug 2014 13:42:49 -0400 Subject: [PATCH 0001/1650] F3 to edit current config file Remaining to do: figure out how to get default config somewhere that bpython can find it (actually install it somewhere) --- bpython/config.py | 4 ++++ bpython/curtsiesfrontend/repl.py | 2 ++ bpython/repl.py | 35 ++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/bpython/config.py b/bpython/config.py index 7a21becfe..df27894b3 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -69,6 +69,7 @@ def loadini(struct, configfile): 'down_one_line': 'C-n', 'exit': '', 'external_editor': 'F7', + 'edit_config': 'F3', 'edit_current_block': 'C-x', 'help': 'F1', 'last_output': 'F9', @@ -99,6 +100,8 @@ def loadini(struct, configfile): "%s\n" % default_config_path()) sys.exit(1) + struct.config_path = config_path + struct.dedent_after = config.getint('general', 'dedent_after') struct.tab_length = config.getint('general', 'tab_length') struct.auto_display_list = config.getboolean('general', @@ -131,6 +134,7 @@ def loadini(struct, configfile): struct.delete_key = config.get('keyboard', 'delete') struct.exit_key = config.get('keyboard', 'exit') struct.last_output_key = config.get('keyboard', 'last_output') + struct.edit_config_key = config.get('keyboard', 'edit_config') struct.edit_current_block_key = config.get('keyboard', 'edit_current_block') struct.external_editor_key = config.get('keyboard', 'external_editor') struct.help_key = config.get('keyboard', 'help') diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 0bac4f2e3..1f39e7699 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -491,6 +491,8 @@ def process_key_event(self, e): greenlet.greenlet(self.pastebin).switch() elif e in key_dispatch[self.config.external_editor_key]: self.send_session_to_external_editor() + elif e in key_dispatch[self.config.edit_config_key]: + greenlet.greenlet(self.edit_config).switch() #TODO add PAD keys hack as in bpython.cli elif e in key_dispatch[self.config.edit_current_block_key]: self.send_current_block_to_external_editor() diff --git a/bpython/repl.py b/bpython/repl.py index 64b14566a..5256f011e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1022,6 +1022,41 @@ def send_to_external_editor(self, text, filename=None): else: return text + def open_in_external_editor(self, filename): + editor_args = shlex.split(self.config.editor) + if subprocess.call(editor_args + [filename]) == 0: + return True + return False + + def edit_config(self): + #TODO put default-config somewhere accessible to this code + DEFAULT = ("[general]\n" + "syntax = True\n" + "[keyboard]\n" + "pastebin = F8\n" + "save = C-s\n") + + open('a.txt', 'w').write(repr(self.config.config_path)) + if not (os.path.isfile(self.config.config_path)): + if self.interact.confirm(_("Config file does not exist - create new from default? (y/N)")): + try: + containing_dir = os.path.dirname(os.path.abspath(self.config.config_path)) + if not os.path.exists(containing_dir): + os.makedirs(containing_dir) + with open(self.config.config_path, 'w') as f: + f.write(DEFAULT) + except (IOError, OSError) as e: + self.interact.notify('error creating file: %r' % e) + raise e + return False + else: + return False + + if self.open_in_external_editor(self.config.config_path): + self.interact.notify('bpython config file edited. Restart bpython for changes to take effect.') + else: + self.interact.notify('error editing config file') + def next_indentation(line, tab_length): """Given a code line, return the indentation of the next line.""" From 60a0687bcb0fc9c8489557f24ac754990de35ed4 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 26 Aug 2014 14:42:58 -0700 Subject: [PATCH 0002/1650] manual_readline refactor --- bpython/curtsiesfrontend/interaction.py | 6 +- bpython/curtsiesfrontend/manual_readline.py | 198 +++++++++++++++----- bpython/curtsiesfrontend/repl.py | 30 ++- bpython/test/test_manual_readline.py | 42 +++++ 4 files changed, 212 insertions(+), 64 deletions(-) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index e67667569..aa2181e07 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -4,7 +4,7 @@ from bpython.repl import Interaction as BpythonInteraction -from bpython.curtsiesfrontend.manual_readline import char_sequences as rl_char_sequences +from bpython.curtsiesfrontend.manual_readline import edit_keys class StatusBar(BpythonInteraction): """StatusBar and Interaction for Repl @@ -68,8 +68,8 @@ def process_event(self, e): elif isinstance(e, events.PasteEvent): for ee in e.events: self.add_normal_character(ee if len(ee) == 1 else ee[-1]) #strip control seq - elif e in rl_char_sequences: - self.cursor_offset_in_line, self._current_line = rl_char_sequences[e](self.cursor_offset_in_line, self._current_line) + elif e in edit_keys: + self.cursor_offset_in_line, self._current_line = edit_keys[e](self.cursor_offset_in_line, self._current_line) elif e == "": raise KeyboardInterrupt() elif e == "": diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 50ab4f6e4..6c8f04410 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -1,57 +1,151 @@ -"""implementations of simple readline control sequences +"""implementations of simple readline edit operations just the ones that fit the model of transforming the current line and the cursor location -in the order of description at http://www.bigsmoke.us/readline/shortcuts""" +based on http://www.bigsmoke.us/readline/shortcuts""" import re -char_sequences = {} +import inspect INDENT = 4 #TODO Allow user config of keybindings for these actions -def on(seq): - def add_to_char_sequences(func): - char_sequences[seq] = func - return func - return add_to_char_sequences - -@on('') -@on('') +class AbstractEdits(object): + + default_kwargs = { + 'line': 'hello world', + 'cursor_offset': 5, + 'cut_buffer': 'there', + 'indent': 4, + } + + def __contains__(self, key): + try: + self[key] + except KeyError: + return False + else: + return True + + def add(self, key, func): + if key in self: + raise ValueError('key %r already has a mapping' % (key,)) + params = inspect.getargspec(func)[0] + args = dict((k, v) for k, v in self.default_kwargs.items() if k in params) + r = func(**args) + if len(r) == 2: + self.simple_edits[key] = func + elif len(r) == 3: + self.cut_buffer_edits[key] = func + else: + raise ValueError('return type of function %r not recognized' % (func,)) + + def add_config_attr(self, config_attr, func): + if config_attr in self.awaiting_config: + raise ValueError('config attrribute %r already has a mapping' % (config_attr,)) + self.awaiting_config[config_attr] = func + + def call(self, key, **kwargs): + func = self[key] + params = inspect.getargspec(func)[0] + args = dict((k, v) for k, v in kwargs.items() if k in params) + return func(**args) + + def __getitem__(self, key): + if key in self.simple_edits: return self.simple_edits[key] + if key in self.cut_buffer_edits: return self.cut_buffer_edits[key] + raise KeyError("key %r not mapped" % (key,)) + + +class UnconfiguredEdits(AbstractEdits): + """Maps key to edit functions, and bins them by what parameters they take. + + Only functions with specific signatures can be added: + * func(**kwargs) -> cursor_offset, line + * func(**kwargs) -> cursor_offset, line, cut_buffer + where kwargs are in among the keys of Edits.default_kwargs + These functions will be run to determine their return type, so no side effects! + + More concrete Edits instances can be created by applying a config with + Edits.mapping_with_config() - this creates a new Edits instance + that uses a config file to assign config_attr bindings. + + Keys can't be added twice, config attributes can't be added twice. + """ + + def __init__(self): + self.simple_edits = {} + self.cut_buffer_edits = {} + self.awaiting_config = {} + + def mapping_with_config(self, config, key_dispatch): + """Creates a new mapping object by applying a config object""" + return ConfiguredEdits(self.simple_edits, self.cut_buffer_edits, + self.awaiting_config, config, key_dispatch) + + def on(self, key=None, config=None): + if (key is None and config is None or + key is not None and config is not None): + raise ValueError("Must use exactly one of key, config") + if key is not None: + def add_to_keybinds(func): + self.add(key, func) + return func + return add_to_keybinds + else: + def add_to_config(func): + self.add_config_attr(config, func) + return func + return add_to_config + +class ConfiguredEdits(AbstractEdits): + def __init__(self, simple_edits, cut_buffer_edits, awaiting_config, config, key_dispatch): + self.simple_edits = dict(simple_edits) + self.cut_buffer_edits = dict(cut_buffer_edits) + for attr, func in awaiting_config.items(): + super(ConfiguredEdits, self).add(key_dispatch[getattr(config, attr)], func) + + def add_config_attr(self, config_attr, func): + raise NotImplementedError("Config already set on this mapping") + + def add(self, key, func): + raise NotImplementedError("Config already set on this mapping") + +edit_keys = UnconfiguredEdits() + +# Because the edits.on decorator runs the functions, functions which depend +# on other functions must be declared after their dependencies + +@edit_keys.on('') +@edit_keys.on('') def left_arrow(cursor_offset, line): return max(0, cursor_offset - 1), line -@on('') -@on('') +@edit_keys.on('') +@edit_keys.on('') def right_arrow(cursor_offset, line): return min(len(line), cursor_offset + 1), line -@on('') -@on('') +@edit_keys.on('') +@edit_keys.on('') def beginning_of_line(cursor_offset, line): return 0, line -@on('') -@on('') +@edit_keys.on('') +@edit_keys.on('') def end_of_line(cursor_offset, line): return len(line), line -@on('') -@on('') -@on('') +@edit_keys.on('') +@edit_keys.on('') +@edit_keys.on('') def forward_word(cursor_offset, line): patt = r"\S\s" match = re.search(patt, line[cursor_offset:]+' ') delta = match.end() - 1 if match else 0 return (cursor_offset + delta, line) -@on('') -@on('') -@on('') -def back_word(cursor_offset, line): - return (last_word_pos(line[:cursor_offset]), line) - def last_word_pos(string): """returns the start index of the last word of given string""" patt = r'\S\s' @@ -59,13 +153,20 @@ def last_word_pos(string): index = match and len(string) - match.end() + 1 return index or 0 -@on('') +@edit_keys.on('') +@edit_keys.on('') +@edit_keys.on('') +def back_word(cursor_offset, line): + return (last_word_pos(line[:cursor_offset]), line) + +@edit_keys.on('') def delete(cursor_offset, line): return (cursor_offset, line[:cursor_offset] + line[cursor_offset+1:]) -@on('') -@on('') +@edit_keys.on('') +@edit_keys.on('') +@edit_keys.on(config='delete_key') def backspace(cursor_offset, line): if cursor_offset == 0: return cursor_offset, line @@ -76,32 +177,30 @@ def backspace(cursor_offset, line): return (cursor_offset - 1, line[:cursor_offset - 1] + line[cursor_offset:]) -@on('') +@edit_keys.on('') +@edit_keys.on(config='clear_line_key') def delete_from_cursor_back(cursor_offset, line): return 0, line[cursor_offset:] -@on('') -def delete_from_cursor_forward(cursor_offset, line): - return cursor_offset, line[:cursor_offset] - -@on('') # option-d +@edit_keys.on('') # option-d def delete_rest_of_word(cursor_offset, line): m = re.search(r'\w\b', line[cursor_offset:]) if not m: return cursor_offset, line return cursor_offset, line[:cursor_offset] + line[m.start()+cursor_offset+1:] -@on('') +@edit_keys.on('') +@edit_keys.on(config='clear_word_key') def delete_word_to_cursor(cursor_offset, line): matches = list(re.finditer(r'\s\S', line[:cursor_offset])) start = matches[-1].start()+1 if matches else 0 return start, line[:start] + line[cursor_offset:] -@on('') +@edit_keys.on('') def yank_prev_prev_killed_text(cursor_offset, line): return cursor_offset, line #TODO Not implemented -@on('') +@edit_keys.on('') def transpose_character_before_cursor(cursor_offset, line): return (min(len(line), cursor_offset + 1), line[:cursor_offset-1] + @@ -109,26 +208,30 @@ def transpose_character_before_cursor(cursor_offset, line): line[cursor_offset - 1] + line[cursor_offset+1:]) -@on('') +@edit_keys.on('') def transpose_word_before_cursor(cursor_offset, line): return cursor_offset, line #TODO Not implemented # bonus functions (not part of readline) -@on('') +@edit_keys.on('') def delete_line(cursor_offset, line): return 0, "" -@on('') +@edit_keys.on('') def uppercase_next_word(cursor_offset, line): return cursor_offset, line #TODO Not implemented -@on('') +@edit_keys.on('') +def delete_from_cursor_forward(cursor_offset, line): + return cursor_offset, line[:cursor_offset] + +@edit_keys.on('') def titlecase_next_word(cursor_offset, line): return cursor_offset, line #TODO Not implemented -@on('') -@on('') +@edit_keys.on('') +@edit_keys.on('') def delete_word_from_cursor_back(cursor_offset, line): """Whatever my option-delete does in bash on my mac""" if not line: @@ -138,10 +241,3 @@ def delete_word_from_cursor_back(cursor_offset, line): return starts[-1], line[:starts[-1]] + line[cursor_offset:] return cursor_offset, line -def get_updated_char_sequences(key_dispatch, config): - updated_char_sequences = dict(char_sequences) - updated_char_sequences[key_dispatch[config.delete_key]] = backspace - updated_char_sequences[key_dispatch[config.clear_word_key]] = delete_word_to_cursor - updated_char_sequences[key_dispatch[config.clear_line_key]] = delete_from_cursor_back - return updated_char_sequences - diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 0bac4f2e3..f83de19b1 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -41,8 +41,7 @@ from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler from bpython.curtsiesfrontend.interaction import StatusBar -from bpython.curtsiesfrontend.manual_readline import char_sequences as rl_char_sequences -from bpython.curtsiesfrontend.manual_readline import get_updated_char_sequences +from bpython.curtsiesfrontend.manual_readline import edit_keys #TODO other autocomplete modes (also fix in other bpython implementations) @@ -72,7 +71,7 @@ class FakeStdin(object): """Stdin object user code references so sys.stdin.read() asked user for interactive input""" - def __init__(self, coderunner, repl, updated_rl_char_sequences=None): + def __init__(self, coderunner, repl, configured_edit_keys=None): self.coderunner = coderunner self.repl = repl self.has_focus = False # whether FakeStdin receives keypress events @@ -80,10 +79,10 @@ def __init__(self, coderunner, repl, updated_rl_char_sequences=None): self.cursor_offset = 0 self.old_num_lines = 0 self.readline_results = [] - if updated_rl_char_sequences: - self.rl_char_sequences = updated_rl_char_sequences + if configured_edit_keys: + self.rl_char_sequences = configured_edit_keys else: - self.rl_char_sequences = rl_char_sequences + self.rl_char_sequences = edit_keys def process_event(self, e): assert self.has_focus @@ -267,7 +266,7 @@ def smarter_request_reload(desc): if config.curtsies_fill_terminal else ''), refresh_request=self.request_refresh ) - self.rl_char_sequences = get_updated_char_sequences(key_dispatch, config) + self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch) logger.debug("starting parent init") super(Repl, self).__init__(interp, config) #TODO bring together all interactive stuff - including current directory in path? @@ -296,7 +295,7 @@ def smarter_request_reload(desc): self.coderunner = CodeRunner(self.interp, self.request_refresh) self.stdout = FakeOutput(self.coderunner, self.send_to_stdout) self.stderr = FakeOutput(self.coderunner, self.send_to_stderr) - self.stdin = FakeStdin(self.coderunner, self, self.rl_char_sequences) + self.stdin = FakeStdin(self.coderunner, self, self.edit_keys) self.request_paint_to_clear_screen = False # next paint should clear screen self.last_events = [None] * 50 @@ -459,8 +458,12 @@ def process_key_event(self, e): self.up_one_line() elif e in ("",) + key_dispatch[self.config.down_one_line_key]: self.down_one_line() - elif e in self.rl_char_sequences: - self.cursor_offset, self.current_line = self.rl_char_sequences[e](self.cursor_offset, self.current_line) + elif e in self.edit_keys: + self.cursor_offset, self.current_line = self.edit_keys[e](self.cursor_offset, self.current_line) + elif e in key_dispatch[self.config.cut_to_buffer_key]: + self.cut_to_buffer() + elif e in key_dispatch[self.config.yank_from_buffer_key]: + self.yank_from_buffer() elif e in key_dispatch[self.config.reimport_key]: self.clear_modules_and_reevaluate() elif e in key_dispatch[self.config.toggle_file_watch_key]: @@ -558,6 +561,13 @@ def on_control_d(self): else: self.current_line = self.current_line[:self.cursor_offset] + self.current_line[self.cursor_offset+1:] + def cut_to_buffer(self): + self.cut_buffer = self.current_line[self.cursor_offset:] + self.current_line = self.current_line[:self.cursor_offset] + + def yank_from_buffer(self): + pass + def up_one_line(self): self.rl_history.enter(self.current_line) self._set_current_line(self.rl_history.back(False, search=self.config.curtsies_right_arrow_completion), diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 004037c0b..cb7e242fe 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -1,3 +1,6 @@ + +from collections import namedtuple + from bpython.curtsiesfrontend.manual_readline import * import unittest @@ -202,5 +205,44 @@ def test_delete_word_from_cursor_back(self): "asd;|", "|"], delete_word_from_cursor_back) +class TestEdits(unittest.TestCase): + + def setUp(self): + self.edits = UnconfiguredEdits() + + def test_seq(self): + f = lambda cursor_offset, line: ('hi', 2) + self.edits.add('a', f) + self.assertTrue('a' in self.edits) + self.assertEqual(self.edits['a'], f) + self.assertEqual(self.edits.call('a', cursor_offset=3, line='hello'), + ('hi', 2)) + self.assertRaises(KeyError, self.edits.__getitem__, 'b') + self.assertRaises(KeyError, self.edits.call, 'b') + + def test_functions_with_bad_signatures(self): + pass #TODO + + def test_functions_with_bad_return_values(self): + pass #TODO + + def test_config(self): + f = lambda cursor_offset, line: ('hi', 2) + g = lambda cursor_offset, line: ('hey', 3) + self.edits.add_config_attr('att', f) + self.assertFalse('att' in self.edits) + class config: att = 'c' + key_dispatch = {'c': 'c'} + configured_edits = self.edits.mapping_with_config(config, key_dispatch) + self.assertTrue(configured_edits.__contains__, 'c') + self.assertFalse('c' in self.edits) + self.assertRaises(NotImplementedError, + configured_edits.add_config_attr, 'att2', g) + self.assertRaises(NotImplementedError, + configured_edits.add, 'd', g) + self.assertEqual(configured_edits.call('c', cursor_offset=5, line='asfd'), + ('hi', 2)) + + if __name__ == '__main__': unittest.main() From c92b043d711185100533f0432818d8cf72ba7c31 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 26 Aug 2014 16:21:36 -0700 Subject: [PATCH 0003/1650] yank working! --- bpython/curtsiesfrontend/manual_readline.py | 58 ++++++++++++++++----- bpython/curtsiesfrontend/repl.py | 25 ++++++--- bpython/test/test_manual_readline.py | 5 ++ 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 6c8f04410..dd51d590e 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -17,7 +17,6 @@ class AbstractEdits(object): 'line': 'hello world', 'cursor_offset': 5, 'cut_buffer': 'there', - 'indent': 4, } def __contains__(self, key): @@ -28,15 +27,22 @@ def __contains__(self, key): else: return True - def add(self, key, func): + def add(self, key, func, overwrite=False): if key in self: - raise ValueError('key %r already has a mapping' % (key,)) + if overwrite: + del self[key] + else: + raise ValueError('key %r already has a mapping' % (key,)) params = inspect.getargspec(func)[0] args = dict((k, v) for k, v in self.default_kwargs.items() if k in params) r = func(**args) if len(r) == 2: + if hasattr(func, 'kills'): + raise ValueError('function %r returns two values, but has a kills attribute' % (func,)) self.simple_edits[key] = func elif len(r) == 3: + if not hasattr(func, 'kills'): + raise ValueError('function %r returns three values, but has no kills attribute' % (func,)) self.cut_buffer_edits[key] = func else: raise ValueError('return type of function %r not recognized' % (func,)) @@ -52,11 +58,20 @@ def call(self, key, **kwargs): args = dict((k, v) for k, v in kwargs.items() if k in params) return func(**args) + def call_without_cut(self, key, **kwargs): + """Looks up the function and calls it, returning only line and cursor offset""" + r = self.call_for_two(key, **kwargs) + return r[:2] + def __getitem__(self, key): if key in self.simple_edits: return self.simple_edits[key] if key in self.cut_buffer_edits: return self.cut_buffer_edits[key] raise KeyError("key %r not mapped" % (key,)) + def __delitem__(self, key): + if key in self.simple_edits: del self.simple_edits[key] + elif key in self.cut_buffer_edits: del self.cut_buffer_edits[key] + else: raise KeyError("key %r not mapped" % (key,)) class UnconfiguredEdits(AbstractEdits): """Maps key to edit functions, and bins them by what parameters they take. @@ -104,7 +119,8 @@ def __init__(self, simple_edits, cut_buffer_edits, awaiting_config, config, key_ self.simple_edits = dict(simple_edits) self.cut_buffer_edits = dict(cut_buffer_edits) for attr, func in awaiting_config.items(): - super(ConfiguredEdits, self).add(key_dispatch[getattr(config, attr)], func) + for key in key_dispatch[getattr(config, attr)]: + super(ConfiguredEdits, self).add(key, func, overwrite=True) def add_config_attr(self, config_attr, func): raise NotImplementedError("Config already set on this mapping") @@ -117,6 +133,14 @@ def add(self, key, func): # Because the edits.on decorator runs the functions, functions which depend # on other functions must be declared after their dependencies +def kills_behind(func): + func.kills = 'behind' + return func + +def kills_ahead(func): + func.kills = 'ahead' + return func + @edit_keys.on('') @edit_keys.on('') def left_arrow(cursor_offset, line): @@ -183,22 +207,29 @@ def delete_from_cursor_back(cursor_offset, line): return 0, line[cursor_offset:] @edit_keys.on('') # option-d +@kills_ahead def delete_rest_of_word(cursor_offset, line): m = re.search(r'\w\b', line[cursor_offset:]) if not m: - return cursor_offset, line - return cursor_offset, line[:cursor_offset] + line[m.start()+cursor_offset+1:] + return cursor_offset, line, '' + return (cursor_offset, line[:cursor_offset] + line[m.start()+cursor_offset+1:], + line[cursor_offset:m.start()+cursor_offset+1]) @edit_keys.on('') @edit_keys.on(config='clear_word_key') +@kills_behind def delete_word_to_cursor(cursor_offset, line): matches = list(re.finditer(r'\s\S', line[:cursor_offset])) start = matches[-1].start()+1 if matches else 0 - return start, line[:start] + line[cursor_offset:] + return start, line[:start] + line[cursor_offset:], line[start:cursor_offset] @edit_keys.on('') -def yank_prev_prev_killed_text(cursor_offset, line): - return cursor_offset, line #TODO Not implemented +def yank_prev_prev_killed_text(cursor_offset, line, cut_buffer): #TODO not implemented - just prev + return cursor_offset+len(cut_buffer), line[:cursor_offset] + cut_buffer + line[cursor_offset:] + +@edit_keys.on(config='yank_from_buffer_key') +def yank_prev_killed_text(cursor_offset, line, cut_buffer): + return cursor_offset+len(cut_buffer), line[:cursor_offset] + cut_buffer + line[cursor_offset:] @edit_keys.on('') def transpose_character_before_cursor(cursor_offset, line): @@ -223,8 +254,9 @@ def uppercase_next_word(cursor_offset, line): return cursor_offset, line #TODO Not implemented @edit_keys.on('') +@kills_ahead def delete_from_cursor_forward(cursor_offset, line): - return cursor_offset, line[:cursor_offset] + return cursor_offset, line[:cursor_offset], line[cursor_offset:] @edit_keys.on('') def titlecase_next_word(cursor_offset, line): @@ -232,12 +264,14 @@ def titlecase_next_word(cursor_offset, line): @edit_keys.on('') @edit_keys.on('') +@kills_behind def delete_word_from_cursor_back(cursor_offset, line): """Whatever my option-delete does in bash on my mac""" if not line: return cursor_offset, line starts = [m.start() for m in list(re.finditer(r'\b\w', line)) if m.start() < cursor_offset] if starts: - return starts[-1], line[:starts[-1]] + line[cursor_offset:] - return cursor_offset, line + return starts[-1], line[:starts[-1]] + line[cursor_offset:], line[starts[-1]:cursor_offset] + return cursor_offset, line, '' + diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f83de19b1..2ee25dc27 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -458,12 +458,17 @@ def process_key_event(self, e): self.up_one_line() elif e in ("",) + key_dispatch[self.config.down_one_line_key]: self.down_one_line() - elif e in self.edit_keys: - self.cursor_offset, self.current_line = self.edit_keys[e](self.cursor_offset, self.current_line) + elif e in ("",): + self.on_control_d() + elif e in self.edit_keys.cut_buffer_edits: + self.readline_kill(e) + elif e in self.edit_keys.simple_edits: + self.cursor_offset, self.current_line = self.edit_keys.call(e, + cursor_offset=self.cursor_offset, + line=self.current_line, + cut_buffer=self.cut_buffer) elif e in key_dispatch[self.config.cut_to_buffer_key]: self.cut_to_buffer() - elif e in key_dispatch[self.config.yank_from_buffer_key]: - self.yank_from_buffer() elif e in key_dispatch[self.config.reimport_key]: self.clear_modules_and_reevaluate() elif e in key_dispatch[self.config.toggle_file_watch_key]: @@ -476,8 +481,6 @@ def process_key_event(self, e): self.pager(self.help_text()) elif e in key_dispatch[self.config.suspend_key]: raise SystemExit() - elif e in ("",): - self.on_control_d() elif e in key_dispatch[self.config.exit_key]: raise SystemExit() elif e in ("\n", "\r", "", "", ""): @@ -504,6 +507,16 @@ def process_key_event(self, e): else: self.add_normal_character(e) + def readline_kill(self, e): + func = self.edit_keys[e] + self.cursor_offset, self.current_line, cut = func(self.cursor_offset, self.current_line) + if self.last_events[-2] == e: # consecutive kill commands are cumulative + if func.kills == 'ahead': self.cut_buffer += cut + elif func.kills == 'behind': self.cut_buffer = cut + self.cut_buffer + else: raise ValueError("cut had value other than 'ahead' or 'behind'") + else: + self.cut_buffer = cut + def on_enter(self, insert_into_history=True): self.cursor_offset = -1 # so the cursor isn't touching a paren self.unhighlight_paren() # in unhighlight_paren diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index cb7e242fe..8c609d575 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -243,6 +243,11 @@ class config: att = 'c' self.assertEqual(configured_edits.call('c', cursor_offset=5, line='asfd'), ('hi', 2)) + def test_actual(self): + class config: att = 'c' + key_dispatch = {'c': 'c'} + configured_edits = self.edits.mapping_with_config(config, key_dispatch) + if __name__ == '__main__': unittest.main() From 46bc5125a4cf0eebf0350c22bfac0d68566add7f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 27 Aug 2014 14:23:07 -0700 Subject: [PATCH 0004/1650] Make sample config used as template for new config file --- bpython/repl.py | 15 +++++---------- sample-config => bpython/sample-config | 0 setup.py | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) rename sample-config => bpython/sample-config (100%) diff --git a/bpython/repl.py b/bpython/repl.py index 5256f011e..11e9f9a28 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1029,25 +1029,20 @@ def open_in_external_editor(self, filename): return False def edit_config(self): - #TODO put default-config somewhere accessible to this code - DEFAULT = ("[general]\n" - "syntax = True\n" - "[keyboard]\n" - "pastebin = F8\n" - "save = C-s\n") - - open('a.txt', 'w').write(repr(self.config.config_path)) if not (os.path.isfile(self.config.config_path)): if self.interact.confirm(_("Config file does not exist - create new from default? (y/N)")): try: + bpython_dir, script_name = os.path.split(__file__) + with open(os.path.join(bpython_dir, "sample-config")) as f: + default_config = f.read() + containing_dir = os.path.dirname(os.path.abspath(self.config.config_path)) if not os.path.exists(containing_dir): os.makedirs(containing_dir) with open(self.config.config_path, 'w') as f: - f.write(DEFAULT) + f.write(default_config) except (IOError, OSError) as e: self.interact.notify('error creating file: %r' % e) - raise e return False else: return False diff --git a/sample-config b/bpython/sample-config similarity index 100% rename from sample-config rename to bpython/sample-config diff --git a/setup.py b/setup.py index 3a050ff63..8de734e8c 100755 --- a/setup.py +++ b/setup.py @@ -184,7 +184,7 @@ def initialize_options(self): packages = packages, data_files = data_files, package_data = { - 'bpython': ['logo.png'], + 'bpython': ['logo.png', 'sample-config'], 'bpython.translations': mo_files, 'bpython.test': ['test.config', 'test.theme'] }, From 434313273b235d8c38379a41c324323e7e7a872f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 27 Aug 2014 14:26:34 -0700 Subject: [PATCH 0005/1650] reference config docs and fix typo in config --- bpython/sample-config | 3 +++ doc/sphinx/source/configuration-options.rst | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/sample-config b/bpython/sample-config index d031556fe..bcb2c0901 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -3,6 +3,9 @@ # By default bpython will look for $XDG_CONFIG_HOME/bpython/config # ($XDG_CONFIG_HOME defaults to ~/.config) or you can specify a file with the # --config option on the command line +# +# see http://docs.bpython-interpreter.org/configuration.html +# for all configurable options # General section tag [general] diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index c25b38a57..1aa79b4af 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -159,7 +159,7 @@ Valid keys are: pastebin ^^^^^^^^ -Default: +Default: F8 last_output ^^^^^^^^^^^ From 812d7ee48457103a3a49624f11c0666305be0444 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 28 Aug 2014 14:40:08 -0700 Subject: [PATCH 0006/1650] fix tests to deal with kill functions --- bpython/test/test_manual_readline.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 8c609d575..96a695310 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -142,11 +142,11 @@ def test_delete_from_cursor_forward(self): line = "everything after this will be deleted" pos = line.find("this") expected = (pos, "everything after ") - result = delete_from_cursor_forward(line.find("this"), line) + result = delete_from_cursor_forward(line.find("this"), line)[:-1] self.assertEquals(expected, result) def test_delete_rest_of_word(self): - self.try_stages(['z|s;df asdf d s;a;a', + self.try_stages_kill(['z|s;df asdf d s;a;a', 'z|;df asdf d s;a;a', 'z| asdf d s;a;a', 'z| d s;a;a', @@ -156,7 +156,7 @@ def test_delete_rest_of_word(self): 'z|'], delete_rest_of_word) def test_delete_word_to_cursor(self): - self.try_stages([ + self.try_stages_kill([ ' a;d sdf ;a;s;d; fjksald|a', ' a;d sdf ;a;s;d; |a', ' a;d sdf |a', @@ -179,6 +179,15 @@ def try_stages(self, strings, func): for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], stages[1:]): self.assertEquals(func(initial_pos, initial), (final_pos, final)) + def try_stages_kill(self, strings, func): + if not all('|' in s for s in strings): + raise ValueError("Need to use '|' to specify cursor") + + stages = [(s.index('|'), s.replace('|', '')) for s in strings] + for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], stages[1:]): + self.assertEquals(func(initial_pos, initial)[:-1], (final_pos, final)) + + def test_transpose_character_before_cursor(self): self.try_stages(["as|df asdf", "ads|f asdf", @@ -194,7 +203,7 @@ def test_backspace(self): self.assertEquals(backspace(3, 'as '), (2, 'as')) def test_delete_word_from_cursor_back(self): - self.try_stages([ + self.try_stages_kill([ "asd;fljk asd;lfjas;dlkfj asdlk jasdf;ljk|", "asd;fljk asd;lfjas;dlkfj asdlk jasdf;|", "asd;fljk asd;lfjas;dlkfj asdlk |", From fc1cf63e3d2c78d9ed21beef60f692b9f2bd1147 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 28 Aug 2014 15:05:06 -0700 Subject: [PATCH 0007/1650] fix send_current_block_to_external_editor --- bpython/curtsiesfrontend/repl.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2ee25dc27..ef294c433 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -594,7 +594,7 @@ def down_one_line(self): self._set_cursor_offset(len(self.current_line), reset_rl_history=False) def process_simple_keypress(self, e): - if e in (u"", u"", u""): + if e in (u"", u"", u"", u"\n", u"\r"): # '\n' necessary for pastes self.on_enter() while self.fake_refresh_requested: self.fake_refresh_requested = False @@ -607,7 +607,7 @@ def process_simple_keypress(self, e): self.add_normal_character(e) def send_current_block_to_external_editor(self, filename=None): - text = self.send_to_external_editor(self.get_current_block()) + text = self.send_to_external_editor(self.get_current_block().encode('utf8')) lines = [line for line in text.split('\n')] while lines and not lines[-1].split(): lines.pop() @@ -616,7 +616,6 @@ def send_current_block_to_external_editor(self, filename=None): with self.in_paste_mode(): for e in events: self.process_simple_keypress(e) - self.current_line = '' self.cursor_offset = len(self.current_line) def send_session_to_external_editor(self, filename=None): From e318b6bbd96aa542c971aa8fbcfd23e8eec87923 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 28 Aug 2014 10:25:35 -0700 Subject: [PATCH 0008/1650] first draft of incremental search (ctrl-r/s) --- bpython/curtsiesfrontend/interaction.py | 1 + bpython/curtsiesfrontend/repl.py | 78 ++++++++++++++++++++----- bpython/test/test_curtsies_painting.py | 4 +- 3 files changed, 68 insertions(+), 15 deletions(-) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index aa2181e07..c58eae11b 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -51,6 +51,7 @@ def has_focus(self): return self.in_prompt or self.in_confirm or self.waiting_for_refresh def message(self, msg): + """Sets a temporary message""" self.message_start_time = time.time() self._message = msg self.refresh_request(time.time() + self.message_time) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index ef294c433..e45438f79 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -298,16 +298,19 @@ def smarter_request_reload(desc): self.stdin = FakeStdin(self.coderunner, self, self.edit_keys) self.request_paint_to_clear_screen = False # next paint should clear screen - self.last_events = [None] * 50 - self.presentation_mode = False - self.paste_mode = False - self.current_match = None - self.list_win_visible = False - self.watching_files = False + self.last_events = [None] * 50 # some commands act differently based on the prev event + # this list doesn't include instances of event.Event, + # only keypress-type events (no refresh screen events etc.) + self.presentation_mode = False # displays prev events in a column on the right hand side + self.paste_mode = False # currently processing a paste event + self.current_match = None # currently tab-selected autocompletion suggestion + self.list_win_visible = False # whether the infobox (suggestions, docstring) is visible + self.watching_files = False # auto reloading turned on + self.special_mode = None # 'reverse_incremental_search' and 'incremental_search' self.original_modules = sys.modules.keys() - self.width = None # will both be set by a window resize event + self.width = None self.height = None self.status_bar.message(banner) @@ -460,6 +463,12 @@ def process_key_event(self, e): self.down_one_line() elif e in ("",): self.on_control_d() + elif e in ("",): + self.incremental_search(reverse=True) + elif e in ("",): + self.incremental_search() + elif e in ("", '') and self.special_mode: + self.add_to_incremental_search(self, backspace=True) elif e in self.edit_keys.cut_buffer_edits: self.readline_kill(e) elif e in self.edit_keys.simple_edits: @@ -507,6 +516,21 @@ def process_key_event(self, e): else: self.add_normal_character(e) + def incremental_search(self, reverse=False): + if self.special_mode == None: + current_line = '' + if reverse: + self.special_mode = 'reverse_incremental_search' + else: + self.special_mode = 'incremental_search' + else: + self._set_current_line(self.rl_history.back(False, search=True) + if reverse else + self.rl_history.forward(False, search=True), + reset_rl_history=False, clear_special_mode=False) + self._set_cursor_offset(len(self.current_line), reset_rl_history=False, + clear_special_mode=False) + def readline_kill(self, e): func = self.edit_keys[e] self.cursor_offset, self.current_line, cut = func(self.cursor_offset, self.current_line) @@ -659,14 +683,35 @@ def toggle_file_watch(self): def add_normal_character(self, char): if len(char) > 1 or is_nop(char): return - self.current_line = (self.current_line[:self.cursor_offset] + - char + - self.current_line[self.cursor_offset:]) - self.cursor_offset += 1 + if self.special_mode == 'reverse_incremental_search': + self.add_to_incremental_search(char) + else: + self.current_line = (self.current_line[:self.cursor_offset] + + char + + self.current_line[self.cursor_offset:]) + self.cursor_offset += 1 if self.config.cli_trim_prompts and self.current_line.startswith(self.ps1): self.current_line = self.current_line[4:] self.cursor_offset = max(0, self.cursor_offset - 4) + def add_to_incremental_search(self, char=None, backspace=False): + if char is None and not backspace: + raise ValueError("must provide a char or set backspace to True") + saved_line = self.rl_history.saved_line + if backspace: + saved_line = saved_line[:-1] + else: + saved_line += char + self.update_completion() + self.rl_history.reset() + self.rl_history.enter(saved_line) + if self.special_mode == 'reverse_incremental_search': + self.incremental_search(reverse=True) + elif self.special_mode == 'incremental_search': + self.incremental_search() + else: + raise ValueError('add_to_incremental_search should only be called in a special mode') + def update_completion(self, tab=False): """Update visible docstring and matches, and possibly hide/show completion box""" #Update autocomplete info; self.matches_iter and self.argspec @@ -866,6 +911,9 @@ def display_buffer_lines(self): @property def display_line_with_prompt(self): """colored line with prompt""" + if self.special_mode == 'reverse_incremental_search': + return func_for_letter(self.config.color_scheme['prompt'])( + '(reverse-i-search)`%s\': ' % (self.rl_history.saved_line,)) + self.current_line_formatted return (func_for_letter(self.config.color_scheme['prompt'])(self.ps1) if self.done else func_for_letter(self.config.color_scheme['prompt_more'])(self.ps2)) + self.current_line_formatted @@ -1085,21 +1133,25 @@ def __repr__(self): def _get_current_line(self): return self._current_line - def _set_current_line(self, line, update_completion=True, reset_rl_history=True): + def _set_current_line(self, line, update_completion=True, reset_rl_history=True, clear_special_mode=True): self._current_line = line if update_completion: self.update_completion() if reset_rl_history: self.rl_history.reset() + if clear_special_mode: + self.special_mode = None current_line = property(_get_current_line, _set_current_line, None, "The current line") def _get_cursor_offset(self): return self._cursor_offset - def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=True): + def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=True, clear_special_mode=True): if update_completion: self.update_completion() if reset_rl_history: self.rl_history.reset() + if clear_special_mode: + self.special_mode = None self._cursor_offset = offset self.update_completion() cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 3ac7d5d20..ed79a4cc8 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -38,8 +38,8 @@ def test_startup(self): def test_enter_text(self): [self.repl.add_normal_character(c) for c in '1 + 1'] - screen = fsarray([cyan('>>> ') + bold(green('1')+cyan(' ')+ - yellow('+') + cyan(' ') + green('1')), cyan('Welcome to')]) + screen = fsarray([cyan('>>> ') + bold(blue('1')+cyan(' ')+ + yellow('+') + cyan(' ') + green('1')), cyan('welcome')]) self.assert_paint(screen, (0, 9)) def test_run_line(self): From fc7ee66a7a4b40a441179ff8d35c55d71aec7a4e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 28 Aug 2014 11:10:44 -0700 Subject: [PATCH 0009/1650] don't include current search string as target for incremental search --- bpython/curtsiesfrontend/repl.py | 20 ++++++++++++++------ bpython/repl.py | 1 + 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index e45438f79..02bff78ab 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -524,12 +524,16 @@ def incremental_search(self, reverse=False): else: self.special_mode = 'incremental_search' else: - self._set_current_line(self.rl_history.back(False, search=True) - if reverse else - self.rl_history.forward(False, search=True), - reset_rl_history=False, clear_special_mode=False) - self._set_cursor_offset(len(self.current_line), reset_rl_history=False, - clear_special_mode=False) + if self.rl_history.saved_line: + line = (self.rl_history.back(False, search=True) + if reverse else + self.rl_history.forward(False, search=True)) + if self.rl_history.is_at_start: + line = '' # incremental search's search string isn't the current line + self._set_current_line(line, + reset_rl_history=False, clear_special_mode=False) + self._set_cursor_offset(len(self.current_line), + reset_rl_history=False, clear_special_mode=False) def readline_kill(self, e): func = self.edit_keys[e] @@ -695,6 +699,10 @@ def add_normal_character(self, char): self.cursor_offset = max(0, self.cursor_offset - 4) def add_to_incremental_search(self, char=None, backspace=False): + """Modify the current search term while in incremental search. + + The only operations allowed in incremental search mode are + adding characters and backspacing.""" if char is None and not backspace: raise ValueError("must provide a char or set backspace to True") saved_line = self.rl_history.saved_line diff --git a/bpython/repl.py b/bpython/repl.py index 64b14566a..b46ea77e1 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -139,6 +139,7 @@ def writetb(self, lines): class History(object): + """Stores readline-style history allows access to it""" def __init__(self, entries=None, duplicates=False): if entries is None: From 169536ddf3dd6d29e6e0c5eb734865d86a947f6f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 28 Aug 2014 13:57:22 -0700 Subject: [PATCH 0010/1650] incremental search and reverse working alright not very tested yet --- bpython/curtsiesfrontend/repl.py | 52 ++++++++++++++------------ bpython/repl.py | 63 +++++++++++++++++++------------- 2 files changed, 66 insertions(+), 49 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 02bff78ab..19cac347c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -307,6 +307,7 @@ def smarter_request_reload(desc): self.list_win_visible = False # whether the infobox (suggestions, docstring) is visible self.watching_files = False # auto reloading turned on self.special_mode = None # 'reverse_incremental_search' and 'incremental_search' + self.incremental_search_target = '' self.original_modules = sys.modules.keys() @@ -510,30 +511,33 @@ def process_key_event(self, e): elif e in key_dispatch[self.config.edit_current_block_key]: self.send_current_block_to_external_editor() elif e in [""]: #ESC - pass + self.special_mode = None elif e in [""]: self.add_normal_character(' ') else: self.add_normal_character(e) - def incremental_search(self, reverse=False): + def incremental_search(self, reverse=False, include_current=False): if self.special_mode == None: - current_line = '' - if reverse: - self.special_mode = 'reverse_incremental_search' - else: - self.special_mode = 'incremental_search' + self.rl_history.enter(self.current_line) + self.incremental_search_target = '' else: - if self.rl_history.saved_line: - line = (self.rl_history.back(False, search=True) + if self.incremental_search_target: + line = (self.rl_history.back(False, search=True, + target=self.incremental_search_target, + include_current=include_current) if reverse else - self.rl_history.forward(False, search=True)) - if self.rl_history.is_at_start: - line = '' # incremental search's search string isn't the current line + self.rl_history.forward(False, search=True, + target=self.incremental_search_target, + include_current=include_current)) self._set_current_line(line, reset_rl_history=False, clear_special_mode=False) self._set_cursor_offset(len(self.current_line), reset_rl_history=False, clear_special_mode=False) + if reverse: + self.special_mode = 'reverse_incremental_search' + else: + self.special_mode = 'incremental_search' def readline_kill(self, e): func = self.edit_keys[e] @@ -687,7 +691,7 @@ def toggle_file_watch(self): def add_normal_character(self, char): if len(char) > 1 or is_nop(char): return - if self.special_mode == 'reverse_incremental_search': + if self.special_mode: self.add_to_incremental_search(char) else: self.current_line = (self.current_line[:self.cursor_offset] + @@ -705,18 +709,14 @@ def add_to_incremental_search(self, char=None, backspace=False): adding characters and backspacing.""" if char is None and not backspace: raise ValueError("must provide a char or set backspace to True") - saved_line = self.rl_history.saved_line if backspace: - saved_line = saved_line[:-1] + self.incremental_search_target = self.incremental_search_target[:-1] else: - saved_line += char - self.update_completion() - self.rl_history.reset() - self.rl_history.enter(saved_line) + self.incremental_search_target += char if self.special_mode == 'reverse_incremental_search': - self.incremental_search(reverse=True) + self.incremental_search(reverse=True, include_current=True) elif self.special_mode == 'incremental_search': - self.incremental_search() + self.incremental_search(include_current=True) else: raise ValueError('add_to_incremental_search should only be called in a special mode') @@ -888,7 +888,10 @@ def current_line_formatted(self): """The colored current line (no prompt, not wrapped)""" if self.config.syntax: fs = bpythonparse(format(self.tokenize(self.current_line), self.formatter)) - if self.rl_history.saved_line in self.current_line: + if self.special_mode: + if self.incremental_search_target in self.current_line: + fs = fmtfuncs.on_magenta(self.incremental_search_target).join(fs.split(self.incremental_search_target)) + elif self.rl_history.saved_line and self.rl_history.saved_line in self.current_line: if self.config.curtsies_right_arrow_completion: fs = fmtfuncs.on_magenta(self.rl_history.saved_line).join(fs.split(self.rl_history.saved_line)) logger.debug('Display line %r -> %r', self.current_line, fs) @@ -921,7 +924,10 @@ def display_line_with_prompt(self): """colored line with prompt""" if self.special_mode == 'reverse_incremental_search': return func_for_letter(self.config.color_scheme['prompt'])( - '(reverse-i-search)`%s\': ' % (self.rl_history.saved_line,)) + self.current_line_formatted + '(reverse-i-search)`%s\': ' % (self.incremental_search_target,)) + self.current_line_formatted + elif self.special_mode == 'incremental_search': + return func_for_letter(self.config.color_scheme['prompt'])( + '(i-search)`%s\': ' % (self.incremental_search_target,)) + self.current_line_formatted return (func_for_letter(self.config.color_scheme['prompt'])(self.ps1) if self.done else func_for_letter(self.config.color_scheme['prompt_more'])(self.ps2)) + self.current_line_formatted diff --git a/bpython/repl.py b/bpython/repl.py index b46ea77e1..6dd499f2d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -139,15 +139,16 @@ def writetb(self, lines): class History(object): - """Stores readline-style history allows access to it""" + """Stores readline-style history and current place in it""" def __init__(self, entries=None, duplicates=False): if entries is None: self.entries = [''] else: self.entries = list(entries) - self.index = 0 - self.saved_line = '' + self.index = 0 # how many lines back in history is currently selected + # where 0 is the saved typed line, 1 the prev entered line + self.saved_line = '' # what was on the prompt before using history self.duplicates = duplicates def append(self, line): @@ -168,58 +169,68 @@ def first(self): self.index = len(self.entries) return self.entries[-self.index] - def back(self, start=True, search=False): + def back(self, start=True, search=False, target=None, include_current=False): """Move one step back in the history.""" + if target is None: + target = self.saved_line if not self.is_at_end: if search: - self.index += self.find_partial_match_backward(self.saved_line) + self.index += self.find_partial_match_backward(target, include_current) elif start: - self.index += self.find_match_backward(self.saved_line) + self.index += self.find_match_backward(target, include_current) else: self.index += 1 + return self.entry + + @property + def entry(self): + """The current entry, which may be the saved line""" return self.entries[-self.index] if self.index else self.saved_line - def find_match_backward(self, search_term): - filtered_list_len = len(self.entries) - self.index - for idx, val in enumerate(reversed(self.entries[:filtered_list_len])): + @property + def entries_by_index(self): + return list(reversed(self.entries + [self.saved_line])) + + def find_match_backward(self, search_term, include_current=False): + for idx, val in enumerate(self.entries_by_index[self.index + (0 if include_current else 1):]): if val.startswith(search_term): - return idx + 1 + return idx + (0 if include_current else 1) return 0 - def find_partial_match_backward(self, search_term): - filtered_list_len = len(self.entries) - self.index - for idx, val in enumerate(reversed(self.entries[:filtered_list_len])): + def find_partial_match_backward(self, search_term, include_current=False): + for idx, val in enumerate(self.entries_by_index[self.index + (0 if include_current else 1):]): if search_term in val: - return idx + 1 + return idx + (0 if include_current else 1) return 0 - def forward(self, start=True, search=False): + def forward(self, start=True, search=False, target=None, include_current=False): """Move one step forward in the history.""" + if target is None: + target = self.saved_line if self.index > 1: if search: - self.index -= self.find_partial_match_forward(self.saved_line) + self.index -= self.find_partial_match_forward(target, include_current) elif start: - self.index -= self.find_match_forward(self.saved_line) + self.index -= self.find_match_forward(target, include_current) else: self.index -= 1 - return self.entries[-self.index] if self.index else self.saved_line + return self.entry else: self.index = 0 return self.saved_line - def find_match_forward(self, search_term): - filtered_list_len = len(self.entries) - self.index + 1 - for idx, val in enumerate(self.entries[filtered_list_len:]): + def find_match_forward(self, search_term, include_current=False): + #TODO these are no longer efficient, because we realize the whole list. Does this matter? + for idx, val in enumerate(reversed(self.entries_by_index[:max(0, self.index - (1 if include_current else 0))])): if val.startswith(search_term): - return idx + 1 + return idx + (0 if include_current else 1) return self.index - def find_partial_match_forward(self, search_term): - filtered_list_len = len(self.entries) - self.index + 1 - for idx, val in enumerate(self.entries[filtered_list_len:]): + def find_partial_match_forward(self, search_term, include_current=False): + for idx, val in enumerate(reversed(self.entries_by_index[:max(0, self.index - (1 if include_current else 0))])): if search_term in val: - return idx + 1 + return idx + (0 if include_current else 1) return self.index From d86d8a8093d56f491b64081c730f6875a72aaf06 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 28 Aug 2014 22:48:18 -0700 Subject: [PATCH 0011/1650] Change hardcoded incremental search keys to meta (esc+) r and s --- bpython/curtsiesfrontend/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 19cac347c..4cbdecb40 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -464,9 +464,9 @@ def process_key_event(self, e): self.down_one_line() elif e in ("",): self.on_control_d() - elif e in ("",): + elif e in ("",): self.incremental_search(reverse=True) - elif e in ("",): + elif e in ("",): self.incremental_search() elif e in ("", '') and self.special_mode: self.add_to_incremental_search(self, backspace=True) From aea557af6f26db8e1ec5766756c7339d06cac965 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 29 Aug 2014 09:11:04 -0700 Subject: [PATCH 0012/1650] fix #366 and fix #367 adds tests for and fixes external communication, and turns off welcome banner if help key is disabled --- bpython/curtsiesfrontend/repl.py | 7 +++++-- bpython/test/test_curtsies_painting.py | 4 ++-- bpython/test/test_curtsies_repl.py | 23 ++++++++++++++++++++++- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index ca58f042a..d7b2d5783 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -238,7 +238,10 @@ def __init__(self, if interp is None: interp = code.InteractiveInterpreter(locals=locals_) if banner is None: - banner = _('Welcome to bpython! Press <%s> for help.') % config.help_key + if config.help_key: + banner = _('Welcome to bpython!') + ' ' + (_('Press <%s> for help.') % config.help_key) + else: + banner = None config.autocomplete_mode = autocomplete.SIMPLE # only one implemented currently if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1: config.cli_suggestion_width = 1 @@ -1261,7 +1264,7 @@ def show_source(self): self.pager(source) def help_text(self): - return self.version_help_text() + '\n' + self.key_help_text() + return (self.version_help_text() + '\n' + self.key_help_text()).encode('utf8') def version_help_text(self): return (('bpython-curtsies version %s' % bpython.__version__) + ' ' + diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index ed79a4cc8..3ac7d5d20 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -38,8 +38,8 @@ def test_startup(self): def test_enter_text(self): [self.repl.add_normal_character(c) for c in '1 + 1'] - screen = fsarray([cyan('>>> ') + bold(blue('1')+cyan(' ')+ - yellow('+') + cyan(' ') + green('1')), cyan('welcome')]) + screen = fsarray([cyan('>>> ') + bold(green('1')+cyan(' ')+ + yellow('+') + cyan(' ') + green('1')), cyan('Welcome to')]) self.assert_paint(screen, (0, 9)) def test_run_line(self): diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index c93467741..d3dfcc4ca 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -1,13 +1,28 @@ import unittest import sys +import os py3 = (sys.version_info[0] == 3) from bpython.curtsiesfrontend import repl +from bpython import config + +def setup_config(conf): + config_struct = config.Struct() + config.loadini(config_struct, os.devnull) + for key, value in conf.items(): + if not hasattr(config_struct, key): + raise ValueError("%r is not a valid config attribute", (key,)) + setattr(config_struct, key, value) + return config_struct class TestCurtsiesRepl(unittest.TestCase): def setUp(self): - self.repl = repl.Repl() + self.config = setup_config({'editor':'true'}) + self.repl = repl.Repl(config=self.config) + os.environ['PAGER'] = 'true' + self.repl.width = 50 + self.repl.height = 20 def test_buffer_finished_will_parse(self): self.repl.buffer = ['1 + 1'] @@ -23,5 +38,11 @@ def test_buffer_finished_will_parse(self): self.repl.buffer = ['def foo(x):', ' return 1', ''] self.assertTrue(self.repl.buffer_finished_will_parse(), (True, True)) + def test_external_communication(self): + self.assertEqual(type(self.repl.help_text()), type(b'')) + self.repl.send_current_block_to_external_editor() + self.repl.send_session_to_external_editor() + + if __name__ == '__main__': unittest.main() From 0533c80c9c80e03503d7b78bac15e08f1de57f53 Mon Sep 17 00:00:00 2001 From: mlauter Date: Mon, 18 Aug 2014 16:33:30 -0400 Subject: [PATCH 0013/1650] stop painting standard error red --- bpython/curtsiesfrontend/repl.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 0bac4f2e3..311c74195 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -787,9 +787,7 @@ def send_to_stderr(self, error): lines = error.split('\n') if lines[-1]: self.current_stdouterr_line += lines[-1] - self.display_lines.extend([func_for_letter(self.config.color_scheme['error'])(line) - for line in sum([paint.display_linize(line, self.width, blank_line=True) - for line in lines[:-1]], [])]) + self.display_lines.extend(sum([paint.display_linize(line, self.width, blank_line=True) for line in lines[:-1]], [])) def send_to_stdin(self, line): if line.endswith('\n'): From 8edeadc7e2dd1de98c7a6e1639800dc0e48061f4 Mon Sep 17 00:00:00 2001 From: mlauter Date: Mon, 18 Aug 2014 18:27:18 -0400 Subject: [PATCH 0014/1650] add new interactive interpreter subclass with pretty pygments traceback coloring --- bpython/curtsiesfrontend/interpreter.py | 168 ++++++++++++++++++++++++ bpython/curtsiesfrontend/repl.py | 3 +- 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 bpython/curtsiesfrontend/interpreter.py diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py new file mode 100644 index 000000000..83586c89a --- /dev/null +++ b/bpython/curtsiesfrontend/interpreter.py @@ -0,0 +1,168 @@ +import code +import traceback +import sys +from pygments.style import Style +from pygments.token import * +from pygments.formatter import Formatter +from curtsies.bpythonparse import parse +from codeop import CommandCompiler, compile_command +from pygments.lexers import get_lexer_by_name +from pygments.styles import get_style_by_name + +default_colors = { + Generic.Error:'R', + Keyword:'d', + Name:'c', + Name.Builtin:'g', + Comment:'b', + String:'m', + Error:'r', + Literal:'d', + Number:'M', + Number.Integer:'d', + Operator:'d', + Punctuation:'d', + Token:'d', + Whitespace:'d', + Token.Punctuation.Parenthesis:'R', + Name.Function:'d', + Name.Class:'d', + } + + +class BPythonFormatter(Formatter): + """This is subclassed from the custom formatter for bpython. + Its format() method receives the tokensource + and outfile params passed to it from the + Pygments highlight() method and slops + them into the appropriate format string + as defined above, then writes to the outfile + object the final formatted string. + + See the Pygments source for more info; it's pretty + straightforward.""" + + def __init__(self, color_scheme, **options): + self.f_strings = {} + for k, v in color_scheme.iteritems(): + self.f_strings[k] = '\x01%s' % (v,) + Formatter.__init__(self, **options) + + def format(self, tokensource, outfile): + o = '' + + for token, text in tokensource: + while token not in self.f_strings: + token = token.parent + o += "%s\x03%s\x04" % (self.f_strings[token], text) + outfile.write(str(parse(o.rstrip()))) + +class Interp(code.InteractiveInterpreter): + def __init__(self, locals=None, outfile=sys.__stderr__): + """Constructor. + + The optional 'locals' argument specifies the dictionary in + which code will be executed; it defaults to a newly created + dictionary with key "__name__" set to "__console__" and key + "__doc__" set to None. + + We include an argument for the outfile to pass to the formatter for it to write to. + + """ + if locals is None: + locals = {"__name__": "__console__", "__doc__": None} + self.locals = locals + self.compile = CommandCompiler() + self.outfile = outfile + + def showsyntaxerror(self, filename=None): + """Display the syntax error that just occurred. + + This doesn't display a stack trace because there isn't one. + + If a filename is given, it is stuffed in the exception instead + of what was there before (because Python's parser always uses + "" when reading from a string). + + The output is written by self.write(), below. + + """ + type, value, sys.last_traceback = sys.exc_info() + sys.last_type = type + sys.last_value = value + if filename and type is SyntaxError: + # Work hard to stuff the correct filename in the exception + try: + msg, (dummy_filename, lineno, offset, line) = value + except: + # Not the format we expect; leave it alone + pass + else: + # Stuff in the right filename + value = SyntaxError(msg, (filename, lineno, offset, line)) + sys.last_value = value + l = traceback.format_exception_only(type, value) + tbtext = ''.join(l) + lexer = get_lexer_by_name("pytb") + traceback_informative_formatter = BPythonFormatter(default_colors) + traceback_code_formatter = BPythonFormatter({Token: ('d')}) + tokens= list(lexer.get_tokens(tbtext)) + no_format_mode = False + cur_line = [] + for token, text in tokens: + if text.endswith('\n'): + cur_line.append((token,text)) + if no_format_mode: + traceback_code_formatter.format(cur_line,self.outfile) + no_format_mode = False + else: + traceback_informative_formatter.format(cur_line,self.outfile) + cur_line = [] + elif text == ' ' and cur_line == []: + no_format_mode = True + cur_line.append((token,text)) + else: + cur_line.append((token,text)) + assert cur_line == [], cur_line + + def showtraceback(self): + """Display the exception that just occurred. + + We remove the first stack item because it is our own code. + + + """ + type, value, tb = sys.exc_info() + sys.last_type = type + sys.last_value = value + sys.last_traceback = tb + tblist = traceback.extract_tb(tb) + del tblist[:1] + l = traceback.format_list(tblist) + if l: + l.insert(0, "Traceback (most recent call last):\n") + l[len(l):] = traceback.format_exception_only(type, value) + tbtext = ''.join(l) + lexer = get_lexer_by_name("pytb", stripall=True) + traceback_informative_formatter = BPythonFormatter(default_colors) + traceback_code_formatter = BPythonFormatter({Token: ('d')}) + tokens= list(lexer.get_tokens(tbtext)) + + no_format_mode = False + cur_line = [] + for token, text in tokens: + if text.endswith('\n'): + cur_line.append((token,text)) + if no_format_mode: + traceback_code_formatter.format(cur_line,self.outfile) + no_format_mode = False + else: + traceback_informative_formatter.format(cur_line,self.outfile) + cur_line = [] + elif text == ' ' and cur_line == []: + no_format_mode = True + cur_line.append((token,text)) + else: + cur_line.append((token,text)) + assert cur_line == [] + diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 311c74195..3626d05e7 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -17,6 +17,7 @@ from pygments import format from pygments.lexers import PythonLexer from pygments.formatters import TerminalFormatter +from interpreter import Interp import blessings @@ -237,7 +238,7 @@ def __init__(self, # would be unsafe because initial # state was passed in if interp is None: - interp = code.InteractiveInterpreter(locals=locals_) + interp = Interp(locals=locals_) if banner is None: banner = _('Welcome to bpython! Press <%s> for help.') % config.help_key config.autocomplete_mode = autocomplete.SIMPLE # only one implemented currently From b5dac4d9888f358c121aad6960e30b55265783a4 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 25 Aug 2014 11:59:25 -0400 Subject: [PATCH 0015/1650] Incorporate new interpreter class into the main repl and use our write traceback method to send to standard error. --- bpython/curtsiesfrontend/interpreter.py | 16 +++++++++------- bpython/curtsiesfrontend/repl.py | 2 ++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 83586c89a..11cd6aa08 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -29,7 +29,6 @@ Name.Class:'d', } - class BPythonFormatter(Formatter): """This is subclassed from the custom formatter for bpython. Its format() method receives the tokensource @@ -58,7 +57,7 @@ def format(self, tokensource, outfile): outfile.write(str(parse(o.rstrip()))) class Interp(code.InteractiveInterpreter): - def __init__(self, locals=None, outfile=sys.__stderr__): + def __init__(self, locals=None): """Constructor. The optional 'locals' argument specifies the dictionary in @@ -73,7 +72,10 @@ def __init__(self, locals=None, outfile=sys.__stderr__): locals = {"__name__": "__console__", "__doc__": None} self.locals = locals self.compile = CommandCompiler() - self.outfile = outfile + + # typically changed after being instantiated + self.write = lambda stuff: sys.stderr.write(stuff) + self.outfile = self def showsyntaxerror(self, filename=None): """Display the syntax error that just occurred. @@ -113,10 +115,10 @@ def showsyntaxerror(self, filename=None): if text.endswith('\n'): cur_line.append((token,text)) if no_format_mode: - traceback_code_formatter.format(cur_line,self.outfile) + traceback_code_formatter.format(cur_line, self.outfile) no_format_mode = False else: - traceback_informative_formatter.format(cur_line,self.outfile) + traceback_informative_formatter.format(cur_line, self.outfile) cur_line = [] elif text == ' ' and cur_line == []: no_format_mode = True @@ -154,10 +156,10 @@ def showtraceback(self): if text.endswith('\n'): cur_line.append((token,text)) if no_format_mode: - traceback_code_formatter.format(cur_line,self.outfile) + traceback_code_formatter.format(cur_line, self.outfile) no_format_mode = False else: - traceback_informative_formatter.format(cur_line,self.outfile) + traceback_informative_formatter.format(cur_line, self.outfile) cur_line = [] elif text == ' ' and cur_line == []: no_format_mode = True diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3626d05e7..f8d9e4b2d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -239,6 +239,7 @@ def __init__(self, # state was passed in if interp is None: interp = Interp(locals=locals_) + interp.writetb = self.send_to_stderr if banner is None: banner = _('Welcome to bpython! Press <%s> for help.') % config.help_key config.autocomplete_mode = autocomplete.SIMPLE # only one implemented currently @@ -1107,6 +1108,7 @@ def reevaluate(self, insert_into_history=False): if not self.weak_rewind: self.interp = self.interp.__class__() + self.interp.writetb = self.send_to_stderr self.coderunner.interp = self.interp self.buffer = [] From 79d55fa759fae3cf8c0ad09a320702462fbc0b13 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 30 Aug 2014 08:35:31 -0700 Subject: [PATCH 0016/1650] more tests, fix #360 Bit of a cop out, but less tests are being skipped now --- bpython/autocomplete.py | 11 +++---- bpython/test/test_autocomplete.py | 47 +++++----------------------- bpython/test/test_manual_readline.py | 10 ++++-- 3 files changed, 21 insertions(+), 47 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 110c0d762..4071e2c13 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -57,6 +57,9 @@ "oct", "hex", "index", "coerce", "enter", "exit"]] +def after_last_dot(name): + return name.rstrip('.').rsplit('.')[-1] + def get_completer(cursor_offset, current_line, locals_, argspec, full_code, mode, complete_magic_methods): """Returns a list of matches and a class for what kind of completion is happening @@ -132,9 +135,7 @@ class ImportCompletion(BaseCompletionType): def matches(cls, cursor_offset, current_line, **kwargs): return importcompletion.complete(cursor_offset, current_line) locate = staticmethod(lineparts.current_word) - @classmethod - def format(cls, name): - return name.rstrip('.').rsplit('.')[-1] + format = staticmethod(after_last_dot) class FilenameCompletion(BaseCompletionType): shown_before_tab = False @@ -193,9 +194,7 @@ def matches(cls, cursor_offset, line, locals_, mode, **kwargs): return matches locate = staticmethod(lineparts.current_dotted_attribute) - @classmethod - def format(cls, name): - return name.rstrip('.').rsplit('.')[-1] + format = staticmethod(after_last_dot) class DictKeyCompletion(BaseCompletionType): locate = staticmethod(lineparts.current_dict_key) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index efedb2501..203297120 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -9,35 +9,10 @@ def skip(f): return lambda self: None - -# Parts of autocompletion to test: +#TODO: Parts of autocompletion to test: # Test that the right matches come back from find_matches (test that priority is correct) # Test the various complete methods (import, filename) to see if right matches # Test that MatchesIterator.substitute correctly subs given a match and a completer -""" - def test_cw(self): - - self.repl.cpos = 2 - self.assertEqual(self.repl.cw(), None) - self.repl.cpos = 0 - - self.repl.s = '' - self.assertEqual(self.repl.cw(), None) - - self.repl.s = "this.is.a.test\t" - self.assertEqual(self.repl.cw(), None) - - s = "this.is.a.test" - self.repl.s = s - self.assertEqual(self.repl.cw(), s) - - s = "\t\tthis.is.a.test" - self.repl.s = s - self.assertEqual(self.repl.cw(), s.lstrip()) - - self.repl.s = "import datetime" - self.assertEqual(self.repl.cw(), 'datetime') -""" class TestSafeEval(unittest.TestCase): def test_catches_syntax_error(self): @@ -46,22 +21,16 @@ def test_catches_syntax_error(self): except: self.fail('safe_eval raises an error') -# make some fake files? Dependency inject? mock? -class TestFilenameCompletion(unittest.TestCase): - pass - - class TestFormatters(unittest.TestCase): - @skip('not done yet') def test_filename(self): - self.assertEqual(autocomplete.last_part_of_filename('abc'), 'abc') - self.assertEqual(autocomplete.last_part_of_filename('abc/'), 'abc/') - self.assertEqual(autocomplete.last_part_of_filename('abc/efg'), 'efg') - self.assertEqual(autocomplete.last_part_of_filename('abc/efg/'), 'efg/') - self.assertEqual(autocomplete.last_part_of_filename('/abc'), 'abc') - self.assertEqual(autocomplete.last_part_of_filename('ab.c/e.f.g/'), 'e.f.g/') + last_part_of_filename = autocomplete.FilenameCompletion.format + self.assertEqual(last_part_of_filename('abc'), 'abc') + self.assertEqual(last_part_of_filename('abc/'), 'abc/') + self.assertEqual(last_part_of_filename('abc/efg'), 'efg') + self.assertEqual(last_part_of_filename('abc/efg/'), 'efg/') + self.assertEqual(last_part_of_filename('/abc'), 'abc') + self.assertEqual(last_part_of_filename('ab.c/e.f.g/'), 'e.f.g/') - @skip('not done yet') def test_attribute(self): self.assertEqual(autocomplete.after_last_dot('abc.edf'), 'edf') diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 96a695310..e577b7331 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -230,10 +230,16 @@ def test_seq(self): self.assertRaises(KeyError, self.edits.call, 'b') def test_functions_with_bad_signatures(self): - pass #TODO + f = lambda something: (1, 2) + self.assertRaises(TypeError, self.edits.add, 'a', f) + g = lambda cursor_offset, line, something, something_else: (1, 2) + self.assertRaises(TypeError, self.edits.add, 'a', g) def test_functions_with_bad_return_values(self): - pass #TODO + f = lambda cursor_offset, line: ('hi',) + self.assertRaises(ValueError, self.edits.add, 'a', f) + g = lambda cursor_offset, line: ('hi', 1, 2, 3) + self.assertRaises(ValueError, self.edits.add, 'b', g) def test_config(self): f = lambda cursor_offset, line: ('hi', 2) From ec8a32ef89a73d1d546160e19432dd45529097ca Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 30 Aug 2014 12:21:05 -0700 Subject: [PATCH 0017/1650] add failing test for #369 --- bpython/curtsies.py | 3 +- bpython/test/test_args.py | 17 +++++++ bpython/test/test_curtsies_painting.py | 1 - bpython/test/test_curtsies_repl.py | 66 ++++++++++++++++++++++---- 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 9f80cb247..d4dc29d07 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -45,7 +45,8 @@ def main(args=None, locals_=None, banner=None): interp = None paste = None if exec_args: - assert options, "don't pass in exec_args without options" + if not options: + raise ValueError("don't pass in exec_args without options") exit_value = 0 if options.type: paste = curtsies.events.PasteEvent() diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index e69de29bb..1d5edc7f7 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -0,0 +1,17 @@ +import os +import sys +import unittest +from mock import Mock, MagicMock +try: + from unittest import skip +except ImportError: + def skip(f): + return lambda self: None + +from bpython import config, repl, cli, autocomplete + +class TestFutureImports(unittest.TestCase): + + def test_interactive(self): + pass + diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 3ac7d5d20..b27693a93 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -18,7 +18,6 @@ def setup_config(): class TestCurtsiesPainting(FormatStringTest): def setUp(self): - self.refresh_requests = [] self.repl = Repl(config=setup_config()) self.repl.rl_history = History() # clear history self.repl.height, self.repl.width = (5, 10) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index d3dfcc4ca..b0c8a4c8d 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -1,10 +1,22 @@ -import unittest -import sys +import code import os +import sys +import tempfile +from contextlib import contextmanager +from StringIO import StringIO + +import unittest +try: + from unittest import skip +except ImportError: + def skip(f): + return lambda self: None + py3 = (sys.version_info[0] == 3) -from bpython.curtsiesfrontend import repl +from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython import config +from bpython import args def setup_config(conf): config_struct = config.Struct() @@ -18,11 +30,7 @@ def setup_config(conf): class TestCurtsiesRepl(unittest.TestCase): def setUp(self): - self.config = setup_config({'editor':'true'}) - self.repl = repl.Repl(config=self.config) - os.environ['PAGER'] = 'true' - self.repl.width = 50 - self.repl.height = 20 + self.repl = create_repl() def test_buffer_finished_will_parse(self): self.repl.buffer = ['1 + 1'] @@ -43,6 +51,48 @@ def test_external_communication(self): self.repl.send_current_block_to_external_editor() self.repl.send_session_to_external_editor() +@contextmanager # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy +def captured_output(): + new_out, new_err = StringIO(), StringIO() + old_out, old_err = sys.stdout, sys.stderr + try: + sys.stdout, sys.stderr = new_out, new_err + yield sys.stdout, sys.stderr + finally: + sys.stdout, sys.stderr = old_out, old_err + +def create_repl(**kwargs): + config = setup_config({'editor':'true'}) + repl = curtsiesrepl.Repl(config=config, **kwargs) + os.environ['PAGER'] = 'true' + repl.width = 50 + repl.height = 20 + return repl + +class TestFutureImports(unittest.TestCase): + + def test_repl(self): + repl = create_repl() + with captured_output() as (out, err): + repl.push('from __future__ import division') + repl.push('1 / 2') + self.assertEqual(out.getvalue(), '0.5\n') + + @skip('Failing - this is issue #369') + def test_interactive(self): + interp = code.InteractiveInterpreter(locals={}) + with captured_output() as (out, err): + with tempfile.NamedTemporaryFile(suffix='.py') as f: + f.write('from __future__ import division\n') + f.write('print 1/2\n') + f.flush() + args.exec_code(interp, [f.name]) + + repl = create_repl(interp=interp) + repl.push('1 / 2') + + self.assertEqual(out.getvalue(), '0.5\n0.5\n') + if __name__ == '__main__': unittest.main() From 84ac7a249e96955c48c5863c7b529e203360de33 Mon Sep 17 00:00:00 2001 From: Alice Chen Date: Wed, 3 Sep 2014 12:13:29 -0400 Subject: [PATCH 0018/1650] Fixed 341 right arrow suggestion color --- bpython/config.py | 1 + bpython/curtsiesfrontend/repl.py | 3 ++- doc/sphinx/source/themes.rst | 2 ++ light.theme | 1 + sample.theme | 1 + 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index df27894b3..9aa01cf86 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -178,6 +178,7 @@ def loadini(struct, configfile): 'paren': 'R', 'prompt': 'c', 'prompt_more': 'g', + 'right_arrow_suggestion': 'K', } if color_scheme_name == 'default': diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d7b2d5783..785252b8f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -948,7 +948,8 @@ def current_cursor_line_without_suggestion(self): @property def current_cursor_line(self): if self.config.curtsies_right_arrow_completion: - return self.current_cursor_line_without_suggestion + fmtfuncs.bold(fmtfuncs.dark((self.current_suggestion))) + return (self.current_cursor_line_without_suggestion + + func_for_letter(self.config.color_scheme['right_arrow_suggestion'])(self.current_suggestion)) else: return self.current_cursor_line_without_suggestion diff --git a/doc/sphinx/source/themes.rst b/doc/sphinx/source/themes.rst index 3dab790eb..c28875be4 100644 --- a/doc/sphinx/source/themes.rst +++ b/doc/sphinx/source/themes.rst @@ -38,6 +38,7 @@ Available Switches * main * prompt * prompt_more +* right_arrow_suggestion Default Theme ------------- @@ -74,6 +75,7 @@ The default theme included in bpython is as follows: main = c prompt = c prompt_more = g + right_arrow_suggestion = K .. :: Footnotes diff --git a/light.theme b/light.theme index 144b48ee1..448d9dc6a 100644 --- a/light.theme +++ b/light.theme @@ -26,3 +26,4 @@ output = b main = b prompt = r prompt_more = g +right_arrow_suggestion = K diff --git a/sample.theme b/sample.theme index ee932be78..af1e3cab7 100644 --- a/sample.theme +++ b/sample.theme @@ -27,3 +27,4 @@ output = w main = c prompt = c prompt_more = g +right_arrow_suggestion = K From b40ce7d5563ca867e7d13cf358c0b2cc3f516259 Mon Sep 17 00:00:00 2001 From: mlauter Date: Thu, 4 Sep 2014 11:39:20 -0400 Subject: [PATCH 0019/1650] stop str-ing in Interp.write, send FmtStr to stderr instead --- bpython/curtsiesfrontend/interpreter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 11cd6aa08..5459692b2 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -36,7 +36,7 @@ class BPythonFormatter(Formatter): Pygments highlight() method and slops them into the appropriate format string as defined above, then writes to the outfile - object the final formatted string. + object the final formatted string. This does not write real strings. It writes format string (FmtStr) objects. See the Pygments source for more info; it's pretty straightforward.""" @@ -54,7 +54,7 @@ def format(self, tokensource, outfile): while token not in self.f_strings: token = token.parent o += "%s\x03%s\x04" % (self.f_strings[token], text) - outfile.write(str(parse(o.rstrip()))) + outfile.write(parse(o.rstrip())) class Interp(code.InteractiveInterpreter): def __init__(self, locals=None): @@ -146,6 +146,8 @@ def showtraceback(self): l[len(l):] = traceback.format_exception_only(type, value) tbtext = ''.join(l) lexer = get_lexer_by_name("pytb", stripall=True) + + traceback_informative_formatter = BPythonFormatter(default_colors) traceback_code_formatter = BPythonFormatter({Token: ('d')}) tokens= list(lexer.get_tokens(tbtext)) From 8aca9a5de7170602ae922c7bc78c49ab09464345 Mon Sep 17 00:00:00 2001 From: mlauter Date: Thu, 4 Sep 2014 12:45:52 -0400 Subject: [PATCH 0020/1650] factor out redundant code from showtraceback and showsyntaxerror --- bpython/curtsiesfrontend/interpreter.py | 27 +++++-------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 5459692b2..7013b436b 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -106,26 +106,7 @@ def showsyntaxerror(self, filename=None): l = traceback.format_exception_only(type, value) tbtext = ''.join(l) lexer = get_lexer_by_name("pytb") - traceback_informative_formatter = BPythonFormatter(default_colors) - traceback_code_formatter = BPythonFormatter({Token: ('d')}) - tokens= list(lexer.get_tokens(tbtext)) - no_format_mode = False - cur_line = [] - for token, text in tokens: - if text.endswith('\n'): - cur_line.append((token,text)) - if no_format_mode: - traceback_code_formatter.format(cur_line, self.outfile) - no_format_mode = False - else: - traceback_informative_formatter.format(cur_line, self.outfile) - cur_line = [] - elif text == ' ' and cur_line == []: - no_format_mode = True - cur_line.append((token,text)) - else: - cur_line.append((token,text)) - assert cur_line == [], cur_line + self.format(tbtext,lexer) def showtraceback(self): """Display the exception that just occurred. @@ -147,7 +128,10 @@ def showtraceback(self): tbtext = ''.join(l) lexer = get_lexer_by_name("pytb", stripall=True) + self.format(tbtext,lexer) + + def format(self, tbtext, lexer): traceback_informative_formatter = BPythonFormatter(default_colors) traceback_code_formatter = BPythonFormatter({Token: ('d')}) tokens= list(lexer.get_tokens(tbtext)) @@ -168,5 +152,4 @@ def showtraceback(self): cur_line.append((token,text)) else: cur_line.append((token,text)) - assert cur_line == [] - + assert cur_line == [], cur_line From 55ce0e753ae5d5ad73f7319e7fd7cb933e1649c4 Mon Sep 17 00:00:00 2001 From: mlauter Date: Thu, 4 Sep 2014 12:46:16 -0400 Subject: [PATCH 0021/1650] add test for interpreter --- bpython/test/test_interpreter.py | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 bpython/test/test_interpreter.py diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py new file mode 100644 index 000000000..bb2cc29cb --- /dev/null +++ b/bpython/test/test_interpreter.py @@ -0,0 +1,42 @@ +import unittest + +from bpython.curtsiesfrontend import interpreter +from curtsies.fmtfuncs import * + +class TestInterpreter(unittest.TestCase): + def test_syntaxerror(self): + i = interpreter.Interp() + a = [] + + def append_to_a(message): + a.append(message) + + i.write = append_to_a + i.runsource('1.1.1.1') + + expected = ''+u''+u' File '+green(u'""')+u', line '+bold(magenta(u'1'))+u'\n'+u' '+u'1.1'+u'.'+u'1.1'+u'\n'+u' '+u' '+u'^'+u'\n'+bold(red(u'SyntaxError'))+u': '+cyan(u'invalid syntax')+u'\n' + + self.assertEquals(str(plain('').join(a)), str(expected)) + self.assertEquals(plain('').join(a), expected) + + def test_traceback(self): + i = interpreter.Interp() + a = [] + + def append_to_a(message): + a.append(message) + + i.write = append_to_a + + def f(): + return 1/0 + + def g(): + return f() + + i.runsource('g()') + + expected = u'Traceback (most recent call last):\n'+''+u' File '+green(u'""')+u', line '+bold (magenta(u'1'))+u', in '+cyan(u'')+u'\n'+''+bold(red(u'NameError'))+u': '+cyan(u"name 'g' is not defined")+u'\n' + + self.assertEquals(str(plain('').join(a)), str(expected)) + self.assertEquals(plain('').join(a), expected) From 51126fa8f72b30ee0262477ae82f609fa86ab114 Mon Sep 17 00:00:00 2001 From: DevOnLinux Date: Thu, 11 Sep 2014 13:05:53 -0400 Subject: [PATCH 0022/1650] Added docs for new keys. fix #338 --- doc/sphinx/source/configuration-options.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 1aa79b4af..386330896 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -175,6 +175,23 @@ Reruns entire session, reloading all modules by clearing the sys.modules cache. .. versionadded:: 0.14 +help +^^^^ +Default: F1 + +Brings up sincerely cheerful description of bpython features and current key bindings. + +.. versionadded:: 0.14 + +toggle_file_watch +^^^^^^^^^^^^^^^^^ +Default: F5 + +Toggles file watching behaviour; re-runs entire bpython session whenever an imported +module is modified. + +.. versionadded:: 0.14 + save ^^^^ Default: C-s From b80b4009c3845c127de7e16a5f8f784c8e2a62ee Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 11 Sep 2014 16:24:27 -0400 Subject: [PATCH 0023/1650] remove trailing whitespace --- doc/sphinx/source/configuration-options.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 386330896..b4810de9e 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -187,7 +187,7 @@ toggle_file_watch ^^^^^^^^^^^^^^^^^ Default: F5 -Toggles file watching behaviour; re-runs entire bpython session whenever an imported +Toggles file watching behaviour; re-runs entire bpython session whenever an imported module is modified. .. versionadded:: 0.14 From d0f0394c62c25d4bdb6daf9cd8c2dc6b891f830d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 11 Sep 2014 20:16:51 -0400 Subject: [PATCH 0024/1650] fix control-c? (Seems to work, could use review) --- bpython/cli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bpython/cli.py b/bpython/cli.py index ebaebe809..78ee771d9 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -999,6 +999,9 @@ def p_key(self, key): elif key == '\x18': return self.send_current_line_to_editor() + elif key == '\x03': + raise KeyboardInterrupt() + elif key[0:3] == 'PAD' and not key in ('PAD0', 'PADSTOP'): pad_keys = { 'PADMINUS': '-', From 2fd97980f15531b8fa33496e620d560b8fffe627 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 8 Sep 2014 16:49:16 -0400 Subject: [PATCH 0025/1650] take back line working for bpythno-curtsies --- bpython/curtsiesfrontend/repl.py | 7 +++++++ bpython/repl.py | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 419c9fce1..e4e854da2 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1193,6 +1193,13 @@ def reprint_line(self, lineno, tokens): logger.debug("calling reprint line with %r %r", lineno, tokens) if self.config.syntax: self.display_buffer[lineno] = bpythonparse(format(tokens, self.formatter)) + + def take_back_buffer_line(self): + self.display_buffer.pop() + self.buffer.pop() + self.cursor_offset = 0 + self.current_line = '' + def reevaluate(self, insert_into_history=False): """bpython.Repl.undo calls this""" if self.watcher: self.watcher.reset() diff --git a/bpython/repl.py b/bpython/repl.py index 221e7a176..76271a38d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -902,8 +902,10 @@ def undo(self, n=1): entries = list(self.rl_history.entries) self.history = self.history[:-n] - - self.reevaluate() + if n == 1 and not self.done: + self.take_back_buffer_line() + else: + self.reevaluate() self.rl_history.entries = entries From cc0ae82cefe0ee393c65763a8c2bd25c4fd0c71e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 8 Sep 2014 17:07:21 -0400 Subject: [PATCH 0026/1650] indentation works for new rewind --- bpython/curtsiesfrontend/repl.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index e4e854da2..b3d78906e 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -735,6 +735,18 @@ def update_completion(self, tab=False): self.current_match = None self.list_win_visible = BpythonRepl.complete(self, tab) + def predicted_indent(self, line): + logger.debug('line is %r', line) + indent = len(re.match(r'[ ]*', line).group()) + if line.endswith(':'): + indent = max(0, indent + self.config.tab_length) + elif line and line.count(' ') == len(line): + indent = max(0, indent - self.config.tab_length) + elif line and ':' not in line and line.strip().startswith(('return', 'pass', 'raise', 'yield')): + indent = max(0, indent - self.config.tab_length) + logger.debug('indent we found was %s', indent) + return indent + def push(self, line, insert_into_history=True): """Push a line of code onto the buffer, start running the buffer @@ -743,14 +755,7 @@ def push(self, line, insert_into_history=True): if self.paste_mode: self.saved_indent = 0 else: - indent = len(re.match(r'[ ]*', line).group()) - if line.endswith(':'): - indent = max(0, indent + self.config.tab_length) - elif line and line.count(' ') == len(line): - indent = max(0, indent - self.config.tab_length) - elif line and ':' not in line and line.strip().startswith(('return', 'pass', 'raise', 'yield')): - indent = max(0, indent - self.config.tab_length) - self.saved_indent = indent + self.saved_indent = self.predicted_indent(line) #current line not added to display buffer if quitting #TODO I don't understand this comment if self.config.syntax: @@ -1197,8 +1202,15 @@ def reprint_line(self, lineno, tokens): def take_back_buffer_line(self): self.display_buffer.pop() self.buffer.pop() - self.cursor_offset = 0 - self.current_line = '' + + if not self.buffer: + self.current_line = '' + self.cursor_offset = 0 + else: + line = self.buffer[-1] + indent = self.predicted_indent(line) + self.current_line = indent * ' ' + self.cursor_offset = len(self.current_line) def reevaluate(self, insert_into_history=False): """bpython.Repl.undo calls this""" From a0f2e2acd03e55f3a95881570b7f96b2597cef76 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 8 Sep 2014 17:14:41 -0400 Subject: [PATCH 0027/1650] Added tests --- bpython/test/test_curtsies_repl.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index b0c8a4c8d..9f1b3ff21 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -93,6 +93,22 @@ def test_interactive(self): self.assertEqual(out.getvalue(), '0.5\n0.5\n') +class TestPredictedIndent(unittest.TestCase): + def setUp(self): + self.repl = create_repl() + + def test_simple(self): + self.assertEqual(self.repl.predicted_indent(''), 0) + self.assertEqual(self.repl.predicted_indent('class Foo:'), 4) + self.assertEqual(self.repl.predicted_indent('class Foo: pass'), 0) + self.assertEqual(self.repl.predicted_indent('def asdf():'), 4) + self.assertEqual(self.repl.predicted_indent('def asdf(): return 7'), 0) + + @skip + def test_complex(self): + self.assertEqual(self.repl.predicted_indent('[a,'), 1) + self.assertEqual(self.repl.predicted_indent('reduce(asdfasdf,'), 7) + if __name__ == '__main__': unittest.main() From f9fa7e8d60edb17528ae4afcf3dd16f8794307ea Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 8 Sep 2014 21:36:50 -0400 Subject: [PATCH 0028/1650] make other frontends work --- bpython/curtsiesfrontend/repl.py | 1 + bpython/repl.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index b3d78906e..4a25ead87 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -736,6 +736,7 @@ def update_completion(self, tab=False): self.list_win_visible = BpythonRepl.complete(self, tab) def predicted_indent(self, line): + #TODO get rid of this! It's repeated code! Combine with Repl. logger.debug('line is %r', line) indent = len(re.match(r'[ ]*', line).group()) if line.endswith(':'): diff --git a/bpython/repl.py b/bpython/repl.py index 76271a38d..c1a654b93 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -902,7 +902,8 @@ def undo(self, n=1): entries = list(self.rl_history.entries) self.history = self.history[:-n] - if n == 1 and not self.done: + if (n == 1 and self.buffer and + hasattr(self, 'take_back_buffer_line')): self.take_back_buffer_line() else: self.reevaluate() From a66607159d206f376ce4722805918a7011527071 Mon Sep 17 00:00:00 2001 From: SusInMotion Date: Thu, 11 Sep 2014 17:17:37 -0400 Subject: [PATCH 0029/1650] pressing prints the last word of the last command to the command line Added test for get last word --- bpython/curtsiesfrontend/repl.py | 25 ++++++++++++++++++++++++- bpython/test/test_curtsies_repl.py | 9 +++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 419c9fce1..fd22d9802 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -46,6 +46,7 @@ #TODO other autocomplete modes (also fix in other bpython implementations) + from curtsies.configfile_keynames import keymap as key_dispatch logger = logging.getLogger(__name__) @@ -87,6 +88,7 @@ def __init__(self, coderunner, repl, configured_edit_keys=None): def process_event(self, e): assert self.has_focus + logger.debug('fake input processing event %r', e) if isinstance(e, events.PasteEvent): for ee in e.events: @@ -100,6 +102,9 @@ def process_event(self, e): self.current_line = '' self.cursor_offset = 0 self.repl.run_code_and_maybe_finish() + elif e in ("",): + self.get_last_word() + elif e in [""]: pass elif e in ['']: @@ -469,6 +474,8 @@ def process_key_event(self, e): self.down_one_line() elif e in ("",): self.on_control_d() + elif e in ("",): + self.get_last_word() elif e in ("",): self.incremental_search(reverse=True) elif e in ("",): @@ -524,6 +531,21 @@ def process_key_event(self, e): else: self.add_normal_character(e) + def get_last_word(self): + + def last_word(line): + if not line: + return '' + return line.split().pop() + + previous_word = last_word(self.rl_history.entry) + word = last_word(self.rl_history.back()) + line=self.current_line + self._set_current_line(line[:len(line)-len(previous_word)]+word, reset_rl_history=False) + + self._set_cursor_offset(self.cursor_offset-len(previous_word)+len(word), reset_rl_history=False) + + def incremental_search(self, reverse=False, include_current=False): if self.special_mode == None: self.rl_history.enter(self.current_line) @@ -808,7 +830,8 @@ def run_code_and_maybe_finish(self, for_code=None): if err: indent = 0 - #TODO This should be printed ABOVE the error that just happened instead + + #TODO This should be printed ABOVE the error that just happened instead # or maybe just thrown away and not shown if self.current_stdouterr_line: self.display_lines.extend(paint.display_linize(self.current_stdouterr_line, self.width)) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index b0c8a4c8d..21a2b3f57 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -51,6 +51,15 @@ def test_external_communication(self): self.repl.send_current_block_to_external_editor() self.repl.send_session_to_external_editor() + def test_get_last_word(self): + self.repl.rl_history.entries=['1','2 3','4 5 6'] + self.repl._set_current_line('abcde') + self.repl.get_last_word() + self.assertEqual(self.repl.current_line,'abcde6') + self.repl.get_last_word() + self.assertEqual(self.repl.current_line,'abcde3') + + @contextmanager # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy def captured_output(): new_out, new_err = StringIO(), StringIO() From 9a6cadb301b927ebb6b6fe352295a781e845e099 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 16 Sep 2014 11:44:40 -0400 Subject: [PATCH 0030/1650] add test documenting bash's meta-. history behavior --- bpython/test/test_curtsies_repl.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 246fce18a..bfaafba45 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -59,6 +59,18 @@ def test_get_last_word(self): self.repl.get_last_word() self.assertEqual(self.repl.current_line,'abcde3') + @skip # this is the behavior of bash - not currently implemented + def test_get_last_word_with_prev_line + self.repl.rl_history.entries=['1','2 3','4 5 6'] + self.repl._set_current_line('abcde') + self.repl.up_one_line() + self.assertEqual(self.repl.current_line,'4 5 6') + self.repl.get_last_word() + self.assertEqual(self.repl.current_line,'4 5 63') + self.repl.get_last_word() + self.assertEqual(self.repl.current_line,'4 5 64') + self.repl.up_one_line() + self.assertEqual(self.repl.current_line,'2 3') @contextmanager # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy def captured_output(): From 9def303254b429db7bc575821b0f7b18e7e2fb76 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 16 Sep 2014 12:11:13 -0400 Subject: [PATCH 0031/1650] fix broken test that broke the build --- bpython/test/test_curtsies_repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index bfaafba45..ab496be2a 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -60,7 +60,7 @@ def test_get_last_word(self): self.assertEqual(self.repl.current_line,'abcde3') @skip # this is the behavior of bash - not currently implemented - def test_get_last_word_with_prev_line + def test_get_last_word_with_prev_line(self): self.repl.rl_history.entries=['1','2 3','4 5 6'] self.repl._set_current_line('abcde') self.repl.up_one_line() From 882c1f371557d1d02a438d5ab3d5af2707cf05b9 Mon Sep 17 00:00:00 2001 From: Tony Wang Date: Thu, 18 Sep 2014 22:31:04 +0800 Subject: [PATCH 0032/1650] consistent return for delete_word_from_cursor_back bpython would crash when option-backspace on empty line --- bpython/curtsiesfrontend/manual_readline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index dd51d590e..4be8bb961 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -268,7 +268,7 @@ def titlecase_next_word(cursor_offset, line): def delete_word_from_cursor_back(cursor_offset, line): """Whatever my option-delete does in bash on my mac""" if not line: - return cursor_offset, line + return cursor_offset, line, '' starts = [m.start() for m in list(re.finditer(r'\b\w', line)) if m.start() < cursor_offset] if starts: return starts[-1], line[:starts[-1]] + line[cursor_offset:], line[starts[-1]:cursor_offset] From ce19978aec2288237b873717867f92e31d443fda Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 18 Sep 2014 13:10:22 -0400 Subject: [PATCH 0033/1650] Add tests for nop case of some readline commands --- bpython/test/test_manual_readline.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index e577b7331..9cd6c286b 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -144,6 +144,7 @@ def test_delete_from_cursor_forward(self): expected = (pos, "everything after ") result = delete_from_cursor_forward(line.find("this"), line)[:-1] self.assertEquals(expected, result) + self.assertEquals(delete_from_cursor_forward(0, ''), (0, '', '')) def test_delete_rest_of_word(self): self.try_stages_kill(['z|s;df asdf d s;a;a', @@ -153,6 +154,7 @@ def test_delete_rest_of_word(self): 'z| s;a;a', 'z|;a;a', 'z|;a', + 'z|', 'z|'], delete_rest_of_word) def test_delete_word_to_cursor(self): @@ -163,7 +165,7 @@ def test_delete_word_to_cursor(self): ' a;d |a', ' |a', '|a', - ], delete_word_to_cursor) + '|a'], delete_word_to_cursor) def test_yank_prev_killed_text(self): pass @@ -212,7 +214,8 @@ def test_delete_word_from_cursor_back(self): "asd;fljk asd;|", "asd;fljk |", "asd;|", - "|"], delete_word_from_cursor_back) + "|", + "|"], delete_word_from_cursor_back) class TestEdits(unittest.TestCase): From 4795738a16ad330a67156df9778ae14ceeb7d00b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 19 Sep 2014 16:26:05 +0200 Subject: [PATCH 0034/1650] Use new JSON interface of bpaste.net and remove the old XML-RPC interface This change adds a dependency on requests. Signed-off-by: Sebastian Ramacher --- bpython/config.py | 8 +++- bpython/repl.py | 45 ++++++++++++--------- doc/sphinx/source/configuration-options.rst | 20 ++++++--- setup.py | 3 +- 4 files changed, 50 insertions(+), 26 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 9aa01cf86..5949b78d9 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -53,9 +53,11 @@ def loadini(struct, configfile): 'tab_length': 4, 'pastebin_confirm': True, 'pastebin_private': False, - 'pastebin_url': 'http://bpaste.net/xmlrpc/', + 'pastebin_url': 'https://bpaste.net/json/new', 'pastebin_private': True, - 'pastebin_show_url': 'http://bpaste.net/show/$paste_id/', + 'pastebin_show_url': 'https://bpaste.net/show/$paste_id', + 'pastebin_removal_url': 'https://bpaste.net/remove/$removal_id', + 'pastebin_expiry': '1week', 'pastebin_helper': '', 'save_append_py': False, 'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')) @@ -144,6 +146,8 @@ def loadini(struct, configfile): struct.pastebin_url = config.get('general', 'pastebin_url') struct.pastebin_private = config.get('general', 'pastebin_private') struct.pastebin_show_url = config.get('general', 'pastebin_show_url') + struct.pastebin_removal_url = config.get('general', 'pastebin_removal_url') + struct.pastebin_expiry = config.get('general', 'pastebin_expiry') struct.pastebin_helper = config.get('general', 'pastebin_helper') struct.cli_suggestion_width = config.getfloat('cli', diff --git a/bpython/repl.py b/bpython/repl.py index c1a654b93..883209509 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -29,6 +29,7 @@ import logging import os import pydoc +import requests import shlex import subprocess import sys @@ -41,8 +42,7 @@ from socket import error as SocketError from string import Template from urllib import quote as urlquote -from urlparse import urlparse -from xmlrpclib import ServerProxy, Error as XMLRPCError +from urlparse import urlparse, urljoin from pygments.token import Token @@ -783,32 +783,41 @@ def do_pastebin(self, s): if self.config.pastebin_helper: return self.do_pastebin_helper(s) else: - return self.do_pastebin_xmlrpc(s) + return self.do_pastebin_json(s) - def do_pastebin_xmlrpc(self, s): - """Upload to pastebin via XML-RPC.""" - try: - pasteservice = ServerProxy(self.config.pastebin_url) - except IOError, e: - self.interact.notify(_("Pastebin error for URL '%s': %s") % - (self.config.pastebin_url, str(e))) - return + def do_pastebin_json(self, s): + """Upload to pastebin via json interface.""" + + url = urljoin(self.config.pastebin_url, '/json/new') + payload = { + 'code': s, + 'lexer': 'pycon', + 'expiry': self.config.pastebin_expiry + } self.interact.notify(_('Posting data to pastebin...')) try: - paste_id = pasteservice.pastes.newPaste('pycon', s, '', '', '', - self.config.pastebin_private) - except (SocketError, XMLRPCError), e: - self.interact.notify(_('Upload failed: %s') % (str(e), ) ) - return + response = requests.post(url, data=payload, verify=True) + response.raise_for_status() + except requests.exceptions.RequestException as exc: + self.interact.notify(_('Upload failed: %s') % (str(exc), )) + return self.prev_pastebin_content = s + data = response.json() paste_url_template = Template(self.config.pastebin_show_url) - paste_id = urlquote(paste_id) + paste_id = urlquote(data['paste_id']) paste_url = paste_url_template.safe_substitute(paste_id=paste_id) + + removal_url_template = Template(self.config.pastebin_removal_url) + removal_id = urlquote(data['removal_id']) + removal_url = removal_url_template.safe_substitute(removal_id=removal_id) + self.prev_pastebin_url = paste_url - self.interact.notify(_('Pastebin URL: %s') % (paste_url, ), 10) + self.interact.notify(_('Pastebin URL: %s - Removal URL: %s') % + (paste_url, removal_url)) + return paste_url def do_pastebin_helper(self, s): diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index b4810de9e..6f2a06c82 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -44,8 +44,8 @@ Soft tab size (default 4, see pep-8) pastebin_url ^^^^^^^^^^^^ The pastebin url to post to (without a trailing slash). This pastebin has to be -a pastebin which uses LodgeIt. Examples are: http://paste.pocoo.org/xmlrpc/ and -http://bpaste.net/xmlrpc/ (default: http://bpaste.net/xmlrpc/) +a pastebin which uses provides a similar interface to ``bpaste.net``'s JSON +interface. (default: https://bpaste.net/json/new) pastebin_private ^^^^^^^^^^^^^^^^ @@ -57,9 +57,19 @@ Default: True). pastebin_show_url ^^^^^^^^^^^^^^^^^ The url under which the new paste can be reached. ``$paste_id`` will be replaced -by the ID of the new paste. Examples are: http://bpaste.net/show/$paste_id/ and -http://paste.pocoo.org/show/$paste_id/ (default: -http://bpaste.net/show/$paste_id/) +by the ID of the new paste. (default: https://bpaste.net/show/$paste_id/) + +pastebin_removal_url +^^^^^^^^^^^^^^^^^^^^ +The url under which a paste can be removed. ``$removal_id`` will be replaced +by the removal ID of the paste. (default: https://bpaste.net/remova/$removal_id/) + +.. versionadded:: 0.14 + +pastebin_expiry +^^^^^^^^^^^^^^^ +Time duration after which a paste should expire. Valid values are ``1day``, +``1week`` and ``1month``. (default: ``1week``) pastebin_helper ^^^^^^^^^^^^^^^ diff --git a/setup.py b/setup.py index 8de734e8c..83fa8ab7f 100755 --- a/setup.py +++ b/setup.py @@ -177,7 +177,8 @@ def initialize_options(self): long_description = """bpython is a fancy interface to the Python interpreter for Unix-like operating systems.""", install_requires = [ - 'pygments' + 'pygments', + 'requests' ], extras_require = extras_require, tests_require = ['mock'], From a1900665bde8510dedb7d4803d672ea6c16a70f3 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 20 Sep 2014 11:13:50 -0400 Subject: [PATCH 0035/1650] add requests to travis config to unbreak automated build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d081b9302..7c3c9c3a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: install: - "python setup.py install" - - "pip install curtsies greenlet pygments" + - "pip install curtsies greenlet pygments requests" script: - cd build/lib/ From 0eda1e13756cca9d0591caae23e8d023189d1a7a Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 20 Sep 2014 12:25:44 -0400 Subject: [PATCH 0036/1650] clarify Python 2/3, install instructions --- doc/sphinx/source/contributing.rst | 39 +++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 131493fd6..a98d07e61 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -5,7 +5,7 @@ Contributing to bpython Thanks for working on bpython! -On the `GitHub issue tracker`_ some issues are labeled bite-size_ +On the `GitHub issue tracker`_ some issues are labeled bite-size_ - these are particularly good ones to start out with. See our section about the :ref:`community` for a list of resources. @@ -16,27 +16,37 @@ to get a question answered depending on the time of day. Getting your development environment set up ------------------------------------------- +bpython supports Python 2.6, 2.7, 3.3 and 3.4. The code is written in Python +2 and transformed for Python 3 with 2to3. This means that it's easier +to work on bpython with Python 2 because it's you can use `python setup.py develop` +(`development mode +`_ installs +by linking to the bpython source instead of copying it over) +in a more straightforward way to have your changes immediately reflected by +your bpython installation and the `bpython`, `bpython-curtsies`, and `bpython-urwid` +commands. + Using a virtual environment is probably a good idea. Create a virtual environment with .. code-block:: bash - $ virtualenv bpython-dev - $ source bpython-dev/bin/activate # this step is necssary every time you work on bpython - - $ deactivate # back to normal system environment + $ virtualenv bpython-dev # determines Python version used + $ source bpython-dev/bin/activate # necssary every time you work on bpython -Fork `bpython` in the GitHub web interface, then clone the repo: +Fork bpython in the GitHub web interface, then clone the repo: .. code-block:: bash $ git clone git@github.com:YOUR_GITHUB_USERNAME/bpython.git + $ # or "git clone https://github.com/YOUR_GITHUB_USERNAME/bpython.git" -Next install this development version of `bpython`: +Next install the dependencies and install your development copy of bpython: .. code-block:: bash - $ pip install pygments curtsies greenlet watchdog urwid # install all the dependencies - $ pip install sphinx mock # development dependencies + $ pip install pygments requests # install required dependencies + $ pip install curtsies greenlet watchdog urwid # install optional dependencies + $ pip install sphinx mock nosetests # development dependencies $ cd bpython $ python setup.py develop @@ -44,15 +54,17 @@ Next install this development version of `bpython`: As a first dev task, I recommend getting `bpython` to print your name every time you hit a specific key. -To run tests: +To run tests from the bpython directory: + +.. code-block:: bash - $ python -m unittest discover bpython + $ nosetests To build the docs: ------------------ -The documentation is included in the regular `bpython` repository. After -checking out the `bpython` repository and installing `sphinx` as described in +The documentation is included in the bpython repository. After +checking out the bpython repository and installing `sphinx` as described in the previous step, you can run the following command in your checkout of the repository to build the documentation: @@ -63,7 +75,6 @@ repository to build the documentation: Afterwards you can point your browser to `doc/sphinx/build/html/index.html`. Don't forget to recreate the HTML after you make changes. - To hack on the site or theme ---------------------------- From a68c8bdf691def3f029359a0e7d89b1ae693a8fd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 20 Sep 2014 19:42:54 +0200 Subject: [PATCH 0037/1650] Add version information Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/configuration-options.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 6f2a06c82..d4391397b 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -71,6 +71,8 @@ pastebin_expiry Time duration after which a paste should expire. Valid values are ``1day``, ``1week`` and ``1month``. (default: ``1week``) +.. versionadded:: 0.14 + pastebin_helper ^^^^^^^^^^^^^^^ From abd5342f3d5f5792af2c580a86a93cd2b2240437 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 20 Sep 2014 19:43:08 +0200 Subject: [PATCH 0038/1650] Remove pastebin_private which is no longer needed --- bpython/config.py | 4 ---- doc/sphinx/source/configuration-options.rst | 7 ------- 2 files changed, 11 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 5949b78d9..38a3b7397 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -52,9 +52,7 @@ def loadini(struct, configfile): 'syntax': True, 'tab_length': 4, 'pastebin_confirm': True, - 'pastebin_private': False, 'pastebin_url': 'https://bpaste.net/json/new', - 'pastebin_private': True, 'pastebin_show_url': 'https://bpaste.net/show/$paste_id', 'pastebin_removal_url': 'https://bpaste.net/remove/$removal_id', 'pastebin_expiry': '1week', @@ -142,9 +140,7 @@ def loadini(struct, configfile): struct.help_key = config.get('keyboard', 'help') struct.pastebin_confirm = config.getboolean('general', 'pastebin_confirm') - struct.pastebin_private = config.getboolean('general', 'pastebin_private') struct.pastebin_url = config.get('general', 'pastebin_url') - struct.pastebin_private = config.get('general', 'pastebin_private') struct.pastebin_show_url = config.get('general', 'pastebin_show_url') struct.pastebin_removal_url = config.get('general', 'pastebin_removal_url') struct.pastebin_expiry = config.get('general', 'pastebin_expiry') diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index d4391397b..d3cc1b7c9 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -47,13 +47,6 @@ The pastebin url to post to (without a trailing slash). This pastebin has to be a pastebin which uses provides a similar interface to ``bpaste.net``'s JSON interface. (default: https://bpaste.net/json/new) -pastebin_private -^^^^^^^^^^^^^^^^ -If the pastebin supports a private option to make a random paste id, use it. -Default: True). - -.. versionadded:: 0.12 - pastebin_show_url ^^^^^^^^^^^^^^^^^ The url under which the new paste can be reached. ``$paste_id`` will be replaced From 51156aab776f82262bd7c4732de3cf3ab3e0dc63 Mon Sep 17 00:00:00 2001 From: David Branner Date: Sat, 20 Sep 2014 07:26:57 -0400 Subject: [PATCH 0039/1650] correct typos change case of original ^D ^D is a plausible command, but it does not work and ^d is meant. make uniform different formats specifying defaults --- doc/sphinx/source/authors.rst | 2 +- doc/sphinx/source/bpdb.rst | 2 +- doc/sphinx/source/community.rst | 6 +++--- doc/sphinx/source/configuration-options.rst | 10 +++++----- doc/sphinx/source/contributing.rst | 2 +- doc/sphinx/source/django.rst | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/sphinx/source/authors.rst b/doc/sphinx/source/authors.rst index 697474ef1..6c3ae627a 100644 --- a/doc/sphinx/source/authors.rst +++ b/doc/sphinx/source/authors.rst @@ -28,5 +28,5 @@ Other contributors are (in alphabetical order): * Marien Zwart A big thanks goes out to all the people who help us out by either submitting -patches, helping us determine problems, our package maintainers and of course +patches, helping us determine problems, our package maintainers, and of course everybody who creates issues for us to fix. diff --git a/doc/sphinx/source/bpdb.rst b/doc/sphinx/source/bpdb.rst index e0b0174f1..eca017139 100644 --- a/doc/sphinx/source/bpdb.rst +++ b/doc/sphinx/source/bpdb.rst @@ -14,4 +14,4 @@ This will drop you into bpdb instead of pdb, which works exactly like pdb except that you can additionally start bpython at the current stack frame by issuing the command `Bpython` or `B`. -You can exit bpython with `^D` to return to bpdb. +You can exit bpython with `^d` to return to bpdb. diff --git a/doc/sphinx/source/community.rst b/doc/sphinx/source/community.rst index 76c8da1cd..2b946a48d 100644 --- a/doc/sphinx/source/community.rst +++ b/doc/sphinx/source/community.rst @@ -15,14 +15,14 @@ worry when you get no response (this does not usually happen) but we are all from Europe and when you get to the channel during our nighttime you might have to wait a while for a response. -Mailinglist +Mailing List ----------- -We have a mailinglist at `google groups `_. +We have a mailing list at `google groups `_. You can post questions there and releases are announced on the mailing list. Website ------- Our main website is http://bpython-interpreter.org/, our documentation can be -found at http://docs.bpython-interpreter.org/ and our pastebin can be found at +found at http://docs.bpython-interpreter.org/, and our pastebin can be found at http://bpaste.net/. diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index d3cc1b7c9..29ee316e4 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -35,7 +35,7 @@ The time between lines before pastemode is activated in seconds (default: 0.02). hist_length ^^^^^^^^^^^ -Number of lines to store in history (set to 0 to disable) (default: 100) +Number of lines to store in history (set to 0 to disable) (default: 100). tab_length ^^^^^^^^^^ @@ -45,24 +45,24 @@ pastebin_url ^^^^^^^^^^^^ The pastebin url to post to (without a trailing slash). This pastebin has to be a pastebin which uses provides a similar interface to ``bpaste.net``'s JSON -interface. (default: https://bpaste.net/json/new) +interface (default: https://bpaste.net/json/new). pastebin_show_url ^^^^^^^^^^^^^^^^^ The url under which the new paste can be reached. ``$paste_id`` will be replaced -by the ID of the new paste. (default: https://bpaste.net/show/$paste_id/) +by the ID of the new paste (default: https://bpaste.net/show/$paste_id/). pastebin_removal_url ^^^^^^^^^^^^^^^^^^^^ The url under which a paste can be removed. ``$removal_id`` will be replaced -by the removal ID of the paste. (default: https://bpaste.net/remova/$removal_id/) +by the removal ID of the paste (default: https://bpaste.net/remova/$removal_id/). .. versionadded:: 0.14 pastebin_expiry ^^^^^^^^^^^^^^^ Time duration after which a paste should expire. Valid values are ``1day``, -``1week`` and ``1month``. (default: ``1week``) +``1week`` and ``1month`` (default: ``1week``). .. versionadded:: 0.14 diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 131493fd6..69a971ca1 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -67,7 +67,7 @@ Don't forget to recreate the HTML after you make changes. To hack on the site or theme ---------------------------- -The site (and it's theme as well) is stored in a separate repository and built using +The site (and its theme as well) is stored in a separate repository and built using pelican. To start hacking on the site you need to start out with a checkout and probably a virtual environment: diff --git a/doc/sphinx/source/django.rst b/doc/sphinx/source/django.rst index ec3f4d9bb..443649505 100644 --- a/doc/sphinx/source/django.rst +++ b/doc/sphinx/source/django.rst @@ -12,7 +12,7 @@ out of the box models and views for a lot of stuff. For those people wanting to use bpython with their Django installation you can follow the following steps. Written by Chanita Siridechkun. The following instructions make bpython try to import a setting module in the current folder -and let django set up it's enviroment with the settings module (if found) if +and let django set up its enviroment with the settings module (if found) if bpython can't find the settings module nothing happens and no enviroment gets set up. From c0bdca04c32ec9ffba0f26af43ed3ee924a57dfe Mon Sep 17 00:00:00 2001 From: Liudmila Date: Wed, 24 Sep 2014 13:59:50 -0400 Subject: [PATCH 0040/1650] Fix get_source_of_current_name to raise exceptions instead of returning None --- bpython/repl.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 221e7a176..2ac02b690 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -591,19 +591,16 @@ def get_source_of_current_name(self): """Return the source code of the object which is bound to the current name in the current input line. Return `None` if the source cannot be found.""" - try: - obj = self.current_func + obj = self.current_func + if obj is None: + line = self.current_line + if line == "": + raise ValueError("Cannot get source of an empty string") + if inspection.is_eval_safe_name(line): + obj = self.get_object(line) if obj is None: - line = self.current_line - if inspection.is_eval_safe_name(line): - obj = self.get_object(line) - if obj is None: - return None - source = inspect.getsource(obj) - except (AttributeError, IOError, NameError, TypeError): - return None - else: - return source + raise NameError("%s is not defined" % line) + return inspect.getsource(obj) #throws an exception that we'll catch def set_docstring(self): self.docstring = None From d4dda9bca60757fe31fb8e8681559172210c56fc Mon Sep 17 00:00:00 2001 From: Liudmila Date: Wed, 24 Sep 2014 14:17:42 -0400 Subject: [PATCH 0041/1650] Fix p_key to catch exceptions from get_source_of_current_name --- bpython/cli.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index ebaebe809..bb7790301 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -968,14 +968,16 @@ def p_key(self, key): return '' elif key in key_dispatch[config.show_source_key]: - source = self.get_source_of_current_name() - if source is not None: + try: + source = self.get_source_of_current_name() if config.highlight_show_source: source = format(PythonLexer().get_tokens(source), TerminalFormatter()) page(source) - else: - self.statusbar.message(_('Cannot show source.')) + except (ValueError, NameError), e: + self.statusbar.message(_(e)) + except (AttributeError, IOError, TypeError), e: + self.statusbar.message(_('Failed to get source: %s' % e)) return '' elif key in ('\n', '\r', 'PADENTER'): From a4552db32357d73d8b2449b971d583d8e1935f6b Mon Sep 17 00:00:00 2001 From: Liudmila Date: Wed, 24 Sep 2014 14:46:54 -0400 Subject: [PATCH 0042/1650] Fix docstring for get_source_of_current_name to match the new behavior --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 2ac02b690..fc5f13d1b 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -589,7 +589,7 @@ def get_args(self): def get_source_of_current_name(self): """Return the source code of the object which is bound to the - current name in the current input line. Return `None` if the + current name in the current input line. Throw exception if the source cannot be found.""" obj = self.current_func if obj is None: From f17eeec03ee7a0d91d84121af2162eca86e064f1 Mon Sep 17 00:00:00 2001 From: Liudmila Date: Wed, 24 Sep 2014 13:59:50 -0400 Subject: [PATCH 0043/1650] Fix get_source_of_current_name to raise exceptions instead of returning None --- bpython/repl.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 883209509..58b0b5396 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -591,19 +591,16 @@ def get_source_of_current_name(self): """Return the source code of the object which is bound to the current name in the current input line. Return `None` if the source cannot be found.""" - try: - obj = self.current_func + obj = self.current_func + if obj is None: + line = self.current_line + if line == "": + raise ValueError("Cannot get source of an empty string") + if inspection.is_eval_safe_name(line): + obj = self.get_object(line) if obj is None: - line = self.current_line - if inspection.is_eval_safe_name(line): - obj = self.get_object(line) - if obj is None: - return None - source = inspect.getsource(obj) - except (AttributeError, IOError, NameError, TypeError): - return None - else: - return source + raise NameError("%s is not defined" % line) + return inspect.getsource(obj) #throws an exception that we'll catch def set_docstring(self): self.docstring = None From 75e8e3593dd0f8b16084a264c09759f8d65cabae Mon Sep 17 00:00:00 2001 From: Liudmila Date: Wed, 24 Sep 2014 14:17:42 -0400 Subject: [PATCH 0044/1650] Fix p_key to catch exceptions from get_source_of_current_name --- bpython/cli.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 78ee771d9..680b40f18 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -968,14 +968,16 @@ def p_key(self, key): return '' elif key in key_dispatch[config.show_source_key]: - source = self.get_source_of_current_name() - if source is not None: + try: + source = self.get_source_of_current_name() if config.highlight_show_source: source = format(PythonLexer().get_tokens(source), TerminalFormatter()) page(source) - else: - self.statusbar.message(_('Cannot show source.')) + except (ValueError, NameError), e: + self.statusbar.message(_(e)) + except (AttributeError, IOError, TypeError), e: + self.statusbar.message(_('Failed to get source: %s' % e)) return '' elif key in ('\n', '\r', 'PADENTER'): From ca74c4607809b895ed75d5e866f64c092dcae44e Mon Sep 17 00:00:00 2001 From: Liudmila Date: Wed, 24 Sep 2014 14:46:54 -0400 Subject: [PATCH 0045/1650] Fix docstring for get_source_of_current_name to match the new behavior --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 58b0b5396..3561faf87 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -589,7 +589,7 @@ def get_args(self): def get_source_of_current_name(self): """Return the source code of the object which is bound to the - current name in the current input line. Return `None` if the + current name in the current input line. Throw exception if the source cannot be found.""" obj = self.current_func if obj is None: From 13eb5188dd3f408b28ab4e12fa964e7c1b10bc1a Mon Sep 17 00:00:00 2001 From: mlauter Date: Wed, 24 Sep 2014 15:27:10 -0400 Subject: [PATCH 0046/1650] fix show_source method of curtsiesfrontend/repl to catch exceptions from get_source_of_current_name --- bpython/curtsiesfrontend/repl.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 1a2ed8e63..487bb3d21 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1300,13 +1300,16 @@ def pager(self, text): self.focus_on_subprocess(command + [tmp.name]) def show_source(self): - source = self.get_source_of_current_name() - if source is None: - self.status_bar.message(_('Cannot show source.')) - else: + try: + source = self.get_source_of_current_name() if self.config.highlight_show_source: - source = format(PythonLexer().get_tokens(source), TerminalFormatter()) + source = format(PythonLexer().get_tokens(source), + TerminalFormatter()) self.pager(source) + except (ValueError, NameError), e: + self.status_bar.message(_(e)) + except (AttributeError, IOError, TypeError), e: + self.status_bar.message(_('Failed to get source: %s' % e)) def help_text(self): return (self.version_help_text() + '\n' + self.key_help_text()).encode('utf8') From 31db4c7e27bc5896ed59562b9c53683397221094 Mon Sep 17 00:00:00 2001 From: mlauter Date: Wed, 24 Sep 2014 16:10:58 -0400 Subject: [PATCH 0047/1650] modify the NameError message displayed by show_source --- bpython/cli.py | 4 +++- bpython/curtsiesfrontend/repl.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 680b40f18..4579545f8 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -974,8 +974,10 @@ def p_key(self, key): source = format(PythonLexer().get_tokens(source), TerminalFormatter()) page(source) - except (ValueError, NameError), e: + except (ValueError), e: self.statusbar.message(_(e)) + except (NameError), e: + self.statusbar.message(_('Cannot get source: %s' % e)) except (AttributeError, IOError, TypeError), e: self.statusbar.message(_('Failed to get source: %s' % e)) return '' diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 487bb3d21..6fd1a3470 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1306,8 +1306,10 @@ def show_source(self): source = format(PythonLexer().get_tokens(source), TerminalFormatter()) self.pager(source) - except (ValueError, NameError), e: + except (ValueError), e: self.status_bar.message(_(e)) + except (NameError), e: + self.status_bar.message(_('Cannot get source: %s' % e)) except (AttributeError, IOError, TypeError), e: self.status_bar.message(_('Failed to get source: %s' % e)) From 1c218a18b5aaa773a6f32c4fa0c69122638032fb Mon Sep 17 00:00:00 2001 From: mlauter Date: Wed, 24 Sep 2014 16:11:43 -0400 Subject: [PATCH 0048/1650] send long error messages originating in inspect.getsource to pager not status_bar --- bpython/cli.py | 2 +- bpython/curtsiesfrontend/repl.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 4579545f8..23484ced0 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -979,7 +979,7 @@ def p_key(self, key): except (NameError), e: self.statusbar.message(_('Cannot get source: %s' % e)) except (AttributeError, IOError, TypeError), e: - self.statusbar.message(_('Failed to get source: %s' % e)) + self.pager.message(_('Failed to get source: %s' % e)) return '' elif key in ('\n', '\r', 'PADENTER'): diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 6fd1a3470..567363834 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1311,7 +1311,7 @@ def show_source(self): except (NameError), e: self.status_bar.message(_('Cannot get source: %s' % e)) except (AttributeError, IOError, TypeError), e: - self.status_bar.message(_('Failed to get source: %s' % e)) + self.pager(_('Failed to get source: %s' % e)) def help_text(self): return (self.version_help_text() + '\n' + self.key_help_text()).encode('utf8') From dab1ccbe62c2639dbd5aa3acb151909797e995db Mon Sep 17 00:00:00 2001 From: mlauter Date: Wed, 24 Sep 2014 17:05:28 -0400 Subject: [PATCH 0049/1650] revert to shortening messages thrown by inspect.getsource before raising --- bpython/repl.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 3561faf87..39885d789 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -600,7 +600,15 @@ def get_source_of_current_name(self): obj = self.get_object(line) if obj is None: raise NameError("%s is not defined" % line) - return inspect.getsource(obj) #throws an exception that we'll catch + try: + inspect.getsource(obj) + except TypeError, e: + msg = e.msg + if "built-in" in msg: + msg.split(">") + raise TypeError("Cannot access source of " % self.current_line) + else: + raise TypeError("No source code found for %s" % self.current_line) def set_docstring(self): self.docstring = None From 73d74b2463354a826348a4efe804141af99d699b Mon Sep 17 00:00:00 2001 From: mlauter Date: Wed, 24 Sep 2014 17:15:50 -0400 Subject: [PATCH 0050/1650] simplify error message raising and handling --- bpython/cli.py | 4 +--- bpython/curtsiesfrontend/repl.py | 6 ++---- bpython/repl.py | 6 ++---- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 23484ced0..5ba09d44e 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -974,12 +974,10 @@ def p_key(self, key): source = format(PythonLexer().get_tokens(source), TerminalFormatter()) page(source) - except (ValueError), e: + except (ValueError, AttributeError, IOError, TypeError), e: self.statusbar.message(_(e)) except (NameError), e: self.statusbar.message(_('Cannot get source: %s' % e)) - except (AttributeError, IOError, TypeError), e: - self.pager.message(_('Failed to get source: %s' % e)) return '' elif key in ('\n', '\r', 'PADENTER'): diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 567363834..79924dac1 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1306,13 +1306,11 @@ def show_source(self): source = format(PythonLexer().get_tokens(source), TerminalFormatter()) self.pager(source) - except (ValueError), e: + except (ValueError, AttributeError, IOError, TypeError), e: self.status_bar.message(_(e)) except (NameError), e: self.status_bar.message(_('Cannot get source: %s' % e)) - except (AttributeError, IOError, TypeError), e: - self.pager(_('Failed to get source: %s' % e)) - + def help_text(self): return (self.version_help_text() + '\n' + self.key_help_text()).encode('utf8') diff --git a/bpython/repl.py b/bpython/repl.py index 39885d789..3130bb5aa 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -598,14 +598,12 @@ def get_source_of_current_name(self): raise ValueError("Cannot get source of an empty string") if inspection.is_eval_safe_name(line): obj = self.get_object(line) - if obj is None: - raise NameError("%s is not defined" % line) + try: inspect.getsource(obj) except TypeError, e: - msg = e.msg + msg = e.message if "built-in" in msg: - msg.split(">") raise TypeError("Cannot access source of " % self.current_line) else: raise TypeError("No source code found for %s" % self.current_line) From 2f9c8cf2edd5cf59ed9f13cb177bb367e53d3662 Mon Sep 17 00:00:00 2001 From: Liudmila Date: Thu, 25 Sep 2014 15:04:31 -0400 Subject: [PATCH 0051/1650] Change error message for empty string --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 7809d2ab0..d8f2e8799 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -595,7 +595,7 @@ def get_source_of_current_name(self): if obj is None: line = self.current_line if line == "": - raise ValueError("Cannot get source of an empty string") + raise ValueError("Nothing to get source of") if inspection.is_eval_safe_name(line): obj = self.get_object(line) try: From e7630aceb767ce79e9ccf01c3b43a547c0d029c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Fri, 26 Sep 2014 20:37:45 +0200 Subject: [PATCH 0052/1650] Use the runsource method of interpreter object in bpython.args.exec_code(). __future__ imports affect the compilation of subsequent code. The interpreter object's compile method keeps track of which __future__ imports it has already seen, while the built-in compile function doesn't. Closes #369. --- bpython/args.py | 4 ++-- bpython/test/test_curtsies_repl.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index dcf3c7b57..ff026357b 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -104,8 +104,8 @@ def exec_code(interpreter, args): sys.argv """ with open(args[0], 'r') as sourcefile: - code_obj = compile(sourcefile.read(), args[0], 'exec') + source = sourcefile.read() old_argv, sys.argv = sys.argv, args sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) - interpreter.runcode(code_obj) + interpreter.runsource(source, args[0], 'exec') sys.argv = old_argv diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index ab496be2a..3bcc21416 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -99,7 +99,6 @@ def test_repl(self): repl.push('1 / 2') self.assertEqual(out.getvalue(), '0.5\n') - @skip('Failing - this is issue #369') def test_interactive(self): interp = code.InteractiveInterpreter(locals={}) with captured_output() as (out, err): From 9a6f047e0c74f5bcd24e7317d95aaae364067252 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 26 Sep 2014 18:11:52 -0400 Subject: [PATCH 0053/1650] fix mistakes trundle pointed out --- bpython/test/test_args.py | 17 ----------------- bpython/test/test_curtsies_repl.py | 2 +- 2 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 bpython/test/test_args.py diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py deleted file mode 100644 index 1d5edc7f7..000000000 --- a/bpython/test/test_args.py +++ /dev/null @@ -1,17 +0,0 @@ -import os -import sys -import unittest -from mock import Mock, MagicMock -try: - from unittest import skip -except ImportError: - def skip(f): - return lambda self: None - -from bpython import config, repl, cli, autocomplete - -class TestFutureImports(unittest.TestCase): - - def test_interactive(self): - pass - diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 3bcc21416..b80ac430a 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -23,7 +23,7 @@ def setup_config(conf): config.loadini(config_struct, os.devnull) for key, value in conf.items(): if not hasattr(config_struct, key): - raise ValueError("%r is not a valid config attribute", (key,)) + raise ValueError("%r is not a valid config attribute" % (key,)) setattr(config_struct, key, value) return config_struct From f05521c3a15ddad2358baff2b011b2f8b3d6931e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Sep 2014 14:02:37 +0200 Subject: [PATCH 0054/1650] Add AppData file Thanks to Richard Hughes and Ryan Lerch for the initial file and forwarding it. We use the description from our homepage instead and also our screenshots. Signed-off-by: Sebastian Ramacher --- MANIFEST.in | 1 + data/bpython.appdata.xml | 37 +++++++++++++++++++++++++++++++++++++ setup.py | 4 +++- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 data/bpython.appdata.xml diff --git a/MANIFEST.in b/MANIFEST.in index 73b6ab574..45293b64a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,7 @@ include CHANGELOG include LICENSE include data/bpython include data/bpython.desktop +include data/bpython.appdata.xml include doc/sphinx/source/conf.py include doc/sphinx/source/*.rst include doc/sphinx/source/logo.png diff --git a/data/bpython.appdata.xml b/data/bpython.appdata.xml new file mode 100644 index 000000000..b24c09368 --- /dev/null +++ b/data/bpython.appdata.xml @@ -0,0 +1,37 @@ + + + + + bpython.desktop + CC0-1.0 + MIT + bpython interpreter + Fancy interface for the Python interpreter + +

+ bpython is a fancy interface to the Python interpreter. It has the + following features: +

    +
  • In-line syntac highlighting.
  • +
  • Readline-like autocomplete with suggesstion displayed as you type.
  • +
  • Expected parameter list for any Python function.
  • +
  • "Rewind" function to pop the last line of code from memory and re-evaluate.
  • +
  • Send the code you've entered off to a pastebin.
  • +
  • Save the code you've entered to a file.
  • +
  • Auto-indentation.
  • +
+

+
+ http://www.bpython-interpreter.org/ + + http://bpython-interpreter.org/images/8.png + http://bpython-interpreter.org/images/1.png + http://bpython-interpreter.org/images/2.png + http://bpython-interpreter.org/images/3.png + http://bpython-interpreter.org/images/4.png + http://bpython-interpreter.org/images/5.png + http://bpython-interpreter.org/images/6.png + http://bpython-interpreter.org/images/7.png + + bpython@googlegroups.com +
diff --git a/setup.py b/setup.py index 83fa8ab7f..05c218807 100755 --- a/setup.py +++ b/setup.py @@ -129,7 +129,9 @@ def initialize_options(self): data_files = [ # desktop shortcut - (os.path.join('share', 'applications'), ['data/bpython.desktop']) + (os.path.join('share', 'applications'), ['data/bpython.desktop']), + # AppData + (os.path.join('share', 'appdata'), ['data/bpython.appdata.xml']) ] data_files.extend(man_pages) From 1684cbf46040a460e74006ceffb97d1046a5df19 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Sep 2014 14:05:49 +0200 Subject: [PATCH 0055/1650] Ignore Sphinx build artefacts Signed-off-by: Sebastian Ramacher --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5cbac053e..62cb91fb7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build/* env .DS_Store .idea/ +doc/sphinx/build/* From 80d35b49913312885540e91e556e0f93478c1593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Tue, 30 Sep 2014 13:52:14 +0200 Subject: [PATCH 0056/1650] Use text mode for temporary Python file in tests. Thanks @myint --- bpython/test/test_curtsies_repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index b80ac430a..d2e545f75 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -102,7 +102,7 @@ def test_repl(self): def test_interactive(self): interp = code.InteractiveInterpreter(locals={}) with captured_output() as (out, err): - with tempfile.NamedTemporaryFile(suffix='.py') as f: + with tempfile.NamedTemporaryFile(mode='w', suffix='.py') as f: f.write('from __future__ import division\n') f.write('print 1/2\n') f.flush() From fd889b38bc17bf00be3237ba016767a4b013dde5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Wed, 1 Oct 2014 11:04:45 +0200 Subject: [PATCH 0057/1650] Small Python 3 fix for issue #369's test. --- bpython/test/test_curtsies_repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index d2e545f75..a83200607 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -104,7 +104,7 @@ def test_interactive(self): with captured_output() as (out, err): with tempfile.NamedTemporaryFile(mode='w', suffix='.py') as f: f.write('from __future__ import division\n') - f.write('print 1/2\n') + f.write('print(1/2)\n') f.flush() args.exec_code(interp, [f.name]) From 86fb57040d9e1313a5209fe931e03b06fc3e05ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Sat, 4 Oct 2014 19:07:09 +0200 Subject: [PATCH 0058/1650] Remove some unused imports. --- bpython/cli.py | 1 - bpython/curtsies.py | 2 -- bpython/curtsiesfrontend/interpreter.py | 5 ++--- bpython/curtsiesfrontend/replpainter.py | 3 +-- bpython/repl.py | 2 -- bpython/test/test_autocomplete.py | 2 -- bpython/test/test_curtsies_painting.py | 1 - bpython/test/test_manual_readline.py | 3 --- 8 files changed, 3 insertions(+), 16 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 5ba09d44e..aa7a7621e 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -84,7 +84,6 @@ from bpython import repl from bpython._py3compat import py3 from bpython.pager import page -from bpython import autocomplete import bpython.args if not py3: diff --git a/bpython/curtsies.py b/bpython/curtsies.py index d4dc29d07..2b6fc3775 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -4,10 +4,8 @@ import logging import sys import time -from subprocess import Popen, PIPE from optparse import Option from itertools import izip -from functools import wraps import curtsies import curtsies.window diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 7013b436b..3be4a25f2 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,13 +1,12 @@ import code import traceback import sys -from pygments.style import Style from pygments.token import * from pygments.formatter import Formatter from curtsies.bpythonparse import parse -from codeop import CommandCompiler, compile_command +from codeop import CommandCompiler from pygments.lexers import get_lexer_by_name -from pygments.styles import get_style_by_name + default_colors = { Generic.Error:'R', diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 56d418e1c..e24467657 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- import logging -import os from curtsies import fsarray, fmtstr from curtsies.bpythonparse import func_for_letter diff --git a/bpython/repl.py b/bpython/repl.py index d8f2e8799..991181f6e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -26,7 +26,6 @@ import codecs import errno import inspect -import logging import os import pydoc import requests @@ -39,7 +38,6 @@ import unicodedata from itertools import takewhile from locale import getpreferredencoding -from socket import error as SocketError from string import Template from urllib import quote as urlquote from urlparse import urlparse, urljoin diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 203297120..fc47a251a 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -1,6 +1,4 @@ from bpython import autocomplete -from functools import partial -import inspect import unittest try: diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index b27693a93..e4736abee 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -1,5 +1,4 @@ # coding: utf8 -import unittest import sys import os diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 9cd6c286b..8bb6eb0da 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -1,6 +1,3 @@ - -from collections import namedtuple - from bpython.curtsiesfrontend.manual_readline import * import unittest From 2ce4a87f90a87c0972c6e436f43ef4e7f5ba06be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Sat, 4 Oct 2014 19:10:25 +0200 Subject: [PATCH 0059/1650] Minor cosmetic changes. --- bpython/autocomplete.py | 4 ++-- bpython/cli.py | 2 +- bpython/curtsiesfrontend/repl.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 4071e2c13..c78d1e25b 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -341,9 +341,9 @@ def safe_eval(expr, namespace): try: obj = eval(expr, namespace) return obj - except (NameError, AttributeError, SyntaxError) as e: + except (NameError, AttributeError, SyntaxError): # If debugging safe_eval, raise this! - # raise e + # raise return SafeEvalFailed def attr_matches(text, namespace, autocomplete_mode): diff --git a/bpython/cli.py b/bpython/cli.py index aa7a7621e..ea8eaf9a7 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -975,7 +975,7 @@ def p_key(self, key): page(source) except (ValueError, AttributeError, IOError, TypeError), e: self.statusbar.message(_(e)) - except (NameError), e: + except NameError, e: self.statusbar.message(_('Cannot get source: %s' % e)) return '' diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 79924dac1..086a67926 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -349,9 +349,9 @@ def new_import(name, globals={}, locals={}, fromlist=[], level=-1): if name in old_module_locations: loc = old_module_locations[name] if self.watching_files: - self.watcher.add_module(old_module_locations[name]) + self.watcher.add_module(loc) else: - self.watcher.add_module_later(old_module_locations[name]) + self.watcher.add_module_later(loc) raise else: if hasattr(m, "__file__"): @@ -1308,9 +1308,9 @@ def show_source(self): self.pager(source) except (ValueError, AttributeError, IOError, TypeError), e: self.status_bar.message(_(e)) - except (NameError), e: + except NameError, e: self.status_bar.message(_('Cannot get source: %s' % e)) - + def help_text(self): return (self.version_help_text() + '\n' + self.key_help_text()).encode('utf8') From af64bb131607c0560b21a0eb49cdf1a158204161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Sat, 4 Oct 2014 19:34:31 +0200 Subject: [PATCH 0060/1650] Remove some duplicated classes in bpython.autocompletion. --- bpython/autocomplete.py | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index c78d1e25b..73868c51f 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -228,46 +228,6 @@ def matches(cls, cursor_offset, line, full_code, **kwargs): start, end, word = r return [name for name in MAGIC_METHODS if name.startswith(word)] -class GlobalCompletion(BaseCompletionType): - @classmethod - def matches(cls, cursor_offset, line, locals_, mode, **kwargs): - r = cls.locate(cursor_offset, line) - if r is None: - return None - start, end, word = r - return global_matches(word, locals_, mode) - locate = staticmethod(lineparts.current_single_word) - -class ParameterNameCompletion(BaseCompletionType): - @classmethod - def matches(cls, cursor_offset, line, argspec, **kwargs): - if not argspec: - return None - r = cls.locate(cursor_offset, line) - if r is None: - return None - start, end, word = r - if argspec: - matches = [name + '=' for name in argspec[1][0] - if isinstance(name, basestring) and name.startswith(word)] - if py3: - matches.extend(name + '=' for name in argspec[1][4] - if name.startswith(word)) - return matches - locate = staticmethod(lineparts.current_word) - -class MagicMethodCompletion(BaseCompletionType): - locate = staticmethod(lineparts.current_method_definition_name) - @classmethod - def matches(cls, cursor_offset, line, full_code, **kwargs): - r = cls.locate(cursor_offset, line) - if r is None: - return None - if 'class' not in full_code: - return None - start, end, word = r - return [name for name in MAGIC_METHODS if name.startswith(word)] - class GlobalCompletion(BaseCompletionType): @classmethod def matches(cls, cursor_offset, line, locals_, mode, **kwargs): From 236e320cec752e5afa27f84a2fb699d52fbe35a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Sat, 4 Oct 2014 19:37:30 +0200 Subject: [PATCH 0061/1650] Fix duplicated test case name. --- bpython/test/test_line_properties.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 6131e0755..0542b1161 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -245,9 +245,10 @@ def test_simple(self): self.assertAccess(' def bar(x, y)|:') self.assertAccess(' def (x, y)') -class TestMethodDefinitionName(LineTestCase): +class TestSingleWord(LineTestCase): def setUp(self): self.func = current_single_word + def test_simple(self): self.assertAccess('foo.bar|') self.assertAccess('.foo|') From 7e51b518de3dace1f40e52e7dba996de0bb48e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Sun, 5 Oct 2014 16:13:41 +0200 Subject: [PATCH 0062/1650] Python 3 fixes for urwid. Closes #243. --- bpython/urwid.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index 582b551e6..cb82b09ae 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -80,7 +80,9 @@ 'd': 'default', } -# Add our keys to the urwid command_map + +def getpreferredencoding(): + return locale.getpreferredencoding() or "ascii" try: @@ -746,7 +748,8 @@ def _populate_completion(self): func_name, args, is_bound, in_arg = self.argspec args, varargs, varkw, defaults = args[:4] if py3: - kwonly, kwonly_defaults = args[4:] + kwonly = self.argspec[1][4] + kwonly_defaults = self.argspec[1][5] or {} else: kwonly, kwonly_defaults = [], {} markup = [('bold name', func_name), @@ -972,14 +975,17 @@ def prompt(self, more): # We need the caption to use unicode as urwid normalizes later # input to be the same type, using ascii as encoding. If the # caption is bytes this breaks typing non-ascii into bpython. - # Currently this decodes using ascii as I do not know where - # ps1 is getting loaded from. If anyone wants to make - # non-ascii prompts work feel free to fix this. if not more: - caption = ('prompt', self.ps1.decode('ascii')) + if py3: + caption = ('prompt', self.ps1) + else: + caption = ('prompt', self.ps1.decode(getpreferredencoding())) self.stdout_hist += self.ps1 else: - caption = ('prompt_more', self.ps2.decode('ascii')) + if py3: + caption = ('prompt_more', self.ps2) + else: + caption = ('prompt_more', self.ps2.decode(getpreferredencoding())) self.stdout_hist += self.ps2 self.edit = BPythonEdit(self.config, caption=caption) @@ -1024,7 +1030,11 @@ def handle_input(self, event): self.history.append(inp) self.edit.make_readonly() # XXX what is this s_hist thing? - self.stdout_hist += inp.encode(locale.getpreferredencoding()) + '\n' + if py3: + self.stdout_hist += inp + else: + self.stdout_hist += inp.encode(locale.getpreferredencoding()) + self.stdout_hist += '\n' self.edit = None # This may take a while, so force a redraw first: self.main_loop.draw_screen() From 28cb9f2c2a551afb11a57f3a13575bee0dfc33de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Sun, 12 Oct 2014 19:12:24 +0200 Subject: [PATCH 0063/1650] Add missing return statement. Fixes #403. --- bpython/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 991181f6e..059342721 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -596,8 +596,8 @@ def get_source_of_current_name(self): raise ValueError("Nothing to get source of") if inspection.is_eval_safe_name(line): obj = self.get_object(line) - try: - inspect.getsource(obj) + try: + return inspect.getsource(obj) except TypeError, e: msg = e.message if "built-in" in msg: From e18e43787bfe26f58c503cc7738c7336409f1150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Sun, 12 Oct 2014 20:06:40 +0200 Subject: [PATCH 0064/1650] Use a specific `SourceNotFound` in get_source_of_current_name(). Instead of leaking all possible exceptions (that the caller has to deal with), use a single `SourceNotFound` exception. --- bpython/cli.py | 7 +++---- bpython/curtsiesfrontend/repl.py | 9 ++++----- bpython/repl.py | 32 ++++++++++++++++++++------------ 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index ea8eaf9a7..46db9d7e1 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -969,14 +969,13 @@ def p_key(self, key): elif key in key_dispatch[config.show_source_key]: try: source = self.get_source_of_current_name() + except repl.SourceNotFound, e: + self.statusbar.message(_(e)) + else: if config.highlight_show_source: source = format(PythonLexer().get_tokens(source), TerminalFormatter()) page(source) - except (ValueError, AttributeError, IOError, TypeError), e: - self.statusbar.message(_(e)) - except NameError, e: - self.statusbar.message(_('Cannot get source: %s' % e)) return '' elif key in ('\n', '\r', 'PADENTER'): diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 086a67926..d827d55b1 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -29,7 +29,7 @@ from curtsies import events import bpython -from bpython.repl import Repl as BpythonRepl +from bpython.repl import Repl as BpythonRepl, SourceNotFound from bpython.config import Struct, loadini, default_config_path from bpython.formatter import BPythonFormatter from bpython import autocomplete, importcompletion @@ -1302,14 +1302,13 @@ def pager(self, text): def show_source(self): try: source = self.get_source_of_current_name() + except SourceNotFound, e: + self.status_bar.message(_(e)) + else: if self.config.highlight_show_source: source = format(PythonLexer().get_tokens(source), TerminalFormatter()) self.pager(source) - except (ValueError, AttributeError, IOError, TypeError), e: - self.status_bar.message(_(e)) - except NameError, e: - self.status_bar.message(_('Cannot get source: %s' % e)) def help_text(self): return (self.version_help_text() + '\n' + self.key_help_text()).encode('utf8') diff --git a/bpython/repl.py b/bpython/repl.py index 059342721..6abc07ec3 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -377,6 +377,10 @@ def file_prompt(self, s): raise NotImplementedError +class SourceNotFound(Exception): + """Exception raised when the requested source could not be found.""" + + class Repl(object): """Implements the necessary guff for a Python-repl-alike interface @@ -587,23 +591,27 @@ def get_args(self): def get_source_of_current_name(self): """Return the source code of the object which is bound to the - current name in the current input line. Throw exception if the + current name in the current input line. Throw `SourceNotFound` if the source cannot be found.""" obj = self.current_func - if obj is None: - line = self.current_line - if line == "": - raise ValueError("Nothing to get source of") - if inspection.is_eval_safe_name(line): - obj = self.get_object(line) try: - return inspect.getsource(obj) + if obj is None: + line = self.current_line + if not line.strip(): + raise SourceNotFound("Nothing to get source of") + if inspection.is_eval_safe_name(line): + obj = self.get_object(line) + return inspect.getsource(obj) + except (AttributeError, NameError), e: + msg = "Cannot get source: " + str(e) + except IOError, e: + msg = str(e) except TypeError, e: - msg = e.message - if "built-in" in msg: - raise TypeError("Cannot access source of " % self.current_line) + if "built-in" in str(e): + msg = "Cannot access source of %r" % (obj, ) else: - raise TypeError("No source code found for %s" % self.current_line) + msg = "No source code found for %s" % (self.current_line, ) + raise SourceNotFound(msg) def set_docstring(self): self.docstring = None From 6dcfc3794c89b9ad1e99bdd2a72f6e041b6f57e3 Mon Sep 17 00:00:00 2001 From: lrraymond13 Date: Tue, 14 Oct 2014 15:50:12 -0400 Subject: [PATCH 0065/1650] Changed 'scroll' to 'curtsies' in help message --- bpython/curtsies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 2b6fc3775..24fe1ceae 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -23,7 +23,7 @@ def main(args=None, locals_=None, banner=None): config, options, exec_args = bpargs.parse(args, ( - 'scroll options', None, [ + 'curtsies options', None, [ Option('--log', '-L', action='store_true', help=_("log debug messages to bpython.log")), Option('--type', '-t', action='store_true', From df80ea147b882b74e0830a901db424aadc7cdeba Mon Sep 17 00:00:00 2001 From: SusInMotion Date: Thu, 11 Sep 2014 17:17:37 -0400 Subject: [PATCH 0066/1650] pressing prints the last word of the last command to the command line Added test for get last word --- bpython/curtsiesfrontend/repl.py | 25 ++++++++++++++++++++++++- bpython/test/test_curtsies_repl.py | 9 +++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 419c9fce1..fd22d9802 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -46,6 +46,7 @@ #TODO other autocomplete modes (also fix in other bpython implementations) + from curtsies.configfile_keynames import keymap as key_dispatch logger = logging.getLogger(__name__) @@ -87,6 +88,7 @@ def __init__(self, coderunner, repl, configured_edit_keys=None): def process_event(self, e): assert self.has_focus + logger.debug('fake input processing event %r', e) if isinstance(e, events.PasteEvent): for ee in e.events: @@ -100,6 +102,9 @@ def process_event(self, e): self.current_line = '' self.cursor_offset = 0 self.repl.run_code_and_maybe_finish() + elif e in ("",): + self.get_last_word() + elif e in [""]: pass elif e in ['']: @@ -469,6 +474,8 @@ def process_key_event(self, e): self.down_one_line() elif e in ("",): self.on_control_d() + elif e in ("",): + self.get_last_word() elif e in ("",): self.incremental_search(reverse=True) elif e in ("",): @@ -524,6 +531,21 @@ def process_key_event(self, e): else: self.add_normal_character(e) + def get_last_word(self): + + def last_word(line): + if not line: + return '' + return line.split().pop() + + previous_word = last_word(self.rl_history.entry) + word = last_word(self.rl_history.back()) + line=self.current_line + self._set_current_line(line[:len(line)-len(previous_word)]+word, reset_rl_history=False) + + self._set_cursor_offset(self.cursor_offset-len(previous_word)+len(word), reset_rl_history=False) + + def incremental_search(self, reverse=False, include_current=False): if self.special_mode == None: self.rl_history.enter(self.current_line) @@ -808,7 +830,8 @@ def run_code_and_maybe_finish(self, for_code=None): if err: indent = 0 - #TODO This should be printed ABOVE the error that just happened instead + + #TODO This should be printed ABOVE the error that just happened instead # or maybe just thrown away and not shown if self.current_stdouterr_line: self.display_lines.extend(paint.display_linize(self.current_stdouterr_line, self.width)) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index b0c8a4c8d..21a2b3f57 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -51,6 +51,15 @@ def test_external_communication(self): self.repl.send_current_block_to_external_editor() self.repl.send_session_to_external_editor() + def test_get_last_word(self): + self.repl.rl_history.entries=['1','2 3','4 5 6'] + self.repl._set_current_line('abcde') + self.repl.get_last_word() + self.assertEqual(self.repl.current_line,'abcde6') + self.repl.get_last_word() + self.assertEqual(self.repl.current_line,'abcde3') + + @contextmanager # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy def captured_output(): new_out, new_err = StringIO(), StringIO() From f721c1d96814ccae19ebaa5b76b6c1c4c146fe94 Mon Sep 17 00:00:00 2001 From: SusInMotion Date: Wed, 1 Oct 2014 19:11:00 -0400 Subject: [PATCH 0067/1650] fixing bug 393 --- bpython/curtsiesfrontend/repl.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index fd22d9802..fff388d5d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -308,6 +308,7 @@ def smarter_request_reload(desc): self.stdin = FakeStdin(self.coderunner, self, self.edit_keys) self.request_paint_to_clear_screen = False # next paint should clear screen + self.inconsistent_history = False # offscreen command yields different result from history self.last_events = [None] * 50 # some commands act differently based on the prev event # this list doesn't include instances of event.Event, # only keypress-type events (no refresh screen events etc.) @@ -1020,7 +1021,11 @@ def paint(self, about_to_exit=False, user_quit=False): else: arr = FSArray(0, width) #TODO test case of current line filling up the whole screen (there aren't enough rows to show it) - + if self.inconsistent_history == True: + msg = "#<---History inconsistent with output shown--->" + arr[0, 0:min(len(msg), width)] = [msg[:width]] + self.inconsistent_history = False +"""FIX THE THING This if may need to go after the second one, but the while applies to both""" if current_line_start_row < 0: #if current line trying to be drawn off the top of the screen logger.debug('#<---History contiguity broken by rewind--->') msg = "#<---History contiguity broken by rewind--->" @@ -1216,10 +1221,13 @@ def reprint_line(self, lineno, tokens): logger.debug("calling reprint line with %r %r", lineno, tokens) if self.config.syntax: self.display_buffer[lineno] = bpythonparse(format(tokens, self.formatter)) + + return self.display_lines def reevaluate(self, insert_into_history=False): """bpython.Repl.undo calls this""" if self.watcher: self.watcher.reset() old_logical_lines = self.history + old_display_lines = self.display_lines self.history = [] self.display_lines = [] @@ -1242,7 +1250,9 @@ def reevaluate(self, insert_into_history=False): self.process_event(events.RefreshRequestEvent()) sys.stdin = self.stdin self.reevaluating = False - + num_lines_onscreen=len(self.lines_for_display)-max(0, self.scroll_offset) + if old_display_lines[:len(self.display_lines)-num_lines_onscreen]!=self.display_lines: + self.inconsistent_history = True self.cursor_offset = 0 self.current_line = '' From a8136bb3df14546f967c0f78d7b97c630b2746ef Mon Sep 17 00:00:00 2001 From: SusInMotion Date: Tue, 7 Oct 2014 15:07:06 -0400 Subject: [PATCH 0068/1650] rewinding some of our changes. --- bpython/curtsies.py | 4 ++-- bpython/curtsiesfrontend/repl.py | 35 +++++++++++++++++++++----------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index d4dc29d07..a90df6031 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -33,10 +33,10 @@ def main(args=None, locals_=None, banner=None): ])) if options.log: handler = logging.FileHandler(filename='bpython.log') - logging.getLogger('curtsies').setLevel(logging.DEBUG) + logging.getLogger('curtsies').setLevel(logging.WARNING) logging.getLogger('curtsies').addHandler(handler) logging.getLogger('curtsies').propagate = False - logging.getLogger('bpython').setLevel(logging.DEBUG) + logging.getLogger('bpython').setLevel(logging.WARNING) logging.getLogger('bpython').addHandler(handler) logging.getLogger('bpython').propagate = False else: diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index fff388d5d..a470e5700 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1021,15 +1021,23 @@ def paint(self, about_to_exit=False, user_quit=False): else: arr = FSArray(0, width) #TODO test case of current line filling up the whole screen (there aren't enough rows to show it) - if self.inconsistent_history == True: - msg = "#<---History inconsistent with output shown--->" - arr[0, 0:min(len(msg), width)] = [msg[:width]] - self.inconsistent_history = False -"""FIX THE THING This if may need to go after the second one, but the while applies to both""" - if current_line_start_row < 0: #if current line trying to be drawn off the top of the screen - logger.debug('#<---History contiguity broken by rewind--->') - msg = "#<---History contiguity broken by rewind--->" - arr[0, 0:min(len(msg), width)] = [msg[:width]] + if self.inconsistent_history == False & current_line_start_row >= 0: + history = paint.paint_history(current_line_start_row, width, self.lines_for_display) + arr[:history.height,:history.width] = history + + else: + if self.inconsistent_history == True: + logger.debug("#<---History inconsistent with output shown--->") + msg = "#<---History inconsistent with output shown--->" + arr[0, 0:min(len(msg), width)] = [msg[:width]] + self.inconsistent_history = False + # self.scroll_offset -= 1 + current_line_start_row = len(self.lines_for_display )- max(-1, self.scroll_offset) + + if current_line_start_row < 0: #if current line trying to be drawn off the top of the screen + logger.debug('#<---History contiguity broken by rewind--->') + msg = "#<---History contiguity broken by rewind--->" + arr[0, 0:min(len(msg), width)] = [msg[:width]] # move screen back up a screen minus a line while current_line_start_row < 0: @@ -1041,9 +1049,7 @@ def paint(self, about_to_exit=False, user_quit=False): if arr.height <= min_height: arr[min_height, 0] = ' ' # force scroll down to hide broken history message - else: - history = paint.paint_history(current_line_start_row, width, self.lines_for_display) - arr[:history.height,:history.width] = history + current_line = paint.paint_current_line(min_height, width, self.current_cursor_line) if user_quit: # quit() or exit() in interp @@ -1252,7 +1258,12 @@ def reevaluate(self, insert_into_history=False): self.reevaluating = False num_lines_onscreen=len(self.lines_for_display)-max(0, self.scroll_offset) if old_display_lines[:len(self.display_lines)-num_lines_onscreen]!=self.display_lines: + old_display_lines_offscreen=old_display_lines[:len(self.display_lines)-num_lines_onscreen] + display_lines_offscreen=self.display_lines[:-num_lines_onscreen] + + if old_display_lines_offscreen!=display_lines_offscreen: self.inconsistent_history = True + self.scroll_offset=self.scroll_offset-max(-1,(len(old_display_lines_offscreen)-len(display_lines_offscreen)+1)) self.cursor_offset = 0 self.current_line = '' From c427c71d2815bef7ada876e1c993cde39085d5f3 Mon Sep 17 00:00:00 2001 From: SusInMotion Date: Tue, 7 Oct 2014 15:23:06 -0400 Subject: [PATCH 0069/1650] shows error message. However, jumps up 2 lines when rewind is pressed the first time. Also does something weird when you try to rewind past the error message --- bpython/curtsiesfrontend/repl.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index a470e5700..7d9331c8d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1257,13 +1257,17 @@ def reevaluate(self, insert_into_history=False): sys.stdin = self.stdin self.reevaluating = False num_lines_onscreen=len(self.lines_for_display)-max(0, self.scroll_offset) + old_display_lines_offscreen=[] + display_lines_offscreen=[] + if old_display_lines[:len(self.display_lines)-num_lines_onscreen]!=self.display_lines: - old_display_lines_offscreen=old_display_lines[:len(self.display_lines)-num_lines_onscreen] - display_lines_offscreen=self.display_lines[:-num_lines_onscreen] - + old_display_lines_offscreen=old_display_lines[:len(self.display_lines)-num_lines_onscreen] + display_lines_offscreen=self.display_lines[:-num_lines_onscreen] + if old_display_lines_offscreen!=display_lines_offscreen: self.inconsistent_history = True self.scroll_offset=self.scroll_offset-max(-1,(len(old_display_lines_offscreen)-len(display_lines_offscreen)+1)) + self.cursor_offset = 0 self.current_line = '' From ccd793f8c11b1fea485300508c9cef08cab92af7 Mon Sep 17 00:00:00 2001 From: SusInMotion Date: Mon, 13 Oct 2014 19:06:58 -0400 Subject: [PATCH 0070/1650] History inconsistent message appears when history offscreen changes --- bpython/curtsiesfrontend/repl.py | 40 ++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 7d9331c8d..f6fd408b7 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -309,6 +309,7 @@ def smarter_request_reload(desc): self.request_paint_to_clear_screen = False # next paint should clear screen self.inconsistent_history = False # offscreen command yields different result from history + self.history_messed_up = True # history error message displayed self.last_events = [None] * 50 # some commands act differently based on the prev event # this list doesn't include instances of event.Event, # only keypress-type events (no refresh screen events etc.) @@ -1022,27 +1023,30 @@ def paint(self, about_to_exit=False, user_quit=False): arr = FSArray(0, width) #TODO test case of current line filling up the whole screen (there aren't enough rows to show it) if self.inconsistent_history == False & current_line_start_row >= 0: + logger.debug("start %i",current_line_start_row) history = paint.paint_history(current_line_start_row, width, self.lines_for_display) arr[:history.height,:history.width] = history else: if self.inconsistent_history == True: - logger.debug("#<---History inconsistent with output shown--->") - msg = "#<---History inconsistent with output shown--->" - arr[0, 0:min(len(msg), width)] = [msg[:width]] self.inconsistent_history = False + #if INCONSISTENT_HISTORY_MSG not in self.display_lines: + logger.debug(INCONSISTENT_HISTORY_MSG) + msg = INCONSISTENT_HISTORY_MSG + arr[0, 0:min(len(msg), width)] = [msg[:width]] # self.scroll_offset -= 1 + current_line_start_row = len(self.lines_for_display )- max(-1, self.scroll_offset) if current_line_start_row < 0: #if current line trying to be drawn off the top of the screen - logger.debug('#<---History contiguity broken by rewind--->') - msg = "#<---History contiguity broken by rewind--->" + logger.debug(CONTIGUITY_BROKEN_MSG) + msg = CONTIGUITY_BROKEN_MSG arr[0, 0:min(len(msg), width)] = [msg[:width]] # move screen back up a screen minus a line - while current_line_start_row < 0: - self.scroll_offset = self.scroll_offset - self.height - current_line_start_row = len(self.lines_for_display) - max(-1, self.scroll_offset) + while current_line_start_row < 0: + self.scroll_offset = self.scroll_offset - self.height + current_line_start_row = len(self.lines_for_display) - max(-1, self.scroll_offset) history = paint.paint_history(max(0, current_line_start_row - 1), width, self.lines_for_display) arr[1:history.height+1,:history.width] = history @@ -1256,17 +1260,19 @@ def reevaluate(self, insert_into_history=False): self.process_event(events.RefreshRequestEvent()) sys.stdin = self.stdin self.reevaluating = False - num_lines_onscreen=len(self.lines_for_display)-max(0, self.scroll_offset) - old_display_lines_offscreen=[] - display_lines_offscreen=[] - - if old_display_lines[:len(self.display_lines)-num_lines_onscreen]!=self.display_lines: - old_display_lines_offscreen=old_display_lines[:len(self.display_lines)-num_lines_onscreen] - display_lines_offscreen=self.display_lines[:-num_lines_onscreen] + + num_lines_onscreen = len(self.lines_for_display) - max(0, self.scroll_offset) + old_display_lines_offscreen = [] + display_lines_offscreen = [] + if old_display_lines[:len(self.display_lines) - num_lines_onscreen]!= self.display_lines: + old_display_lines_offscreen = old_display_lines[:len(self.display_lines) - num_lines_onscreen] + display_lines_offscreen = self.display_lines[:-num_lines_onscreen] - if old_display_lines_offscreen!=display_lines_offscreen: + if old_display_lines_offscreen != display_lines_offscreen: + self.scroll_offset=self.scroll_offset-(len(old_display_lines)-len(self.display_lines)) + self.inconsistent_history = True - self.scroll_offset=self.scroll_offset-max(-1,(len(old_display_lines_offscreen)-len(display_lines_offscreen)+1)) + #self.scroll_offset = self.scroll_offset - max(-1,(len(old_display_lines_offscreen)-len(display_lines_offscreen)+1)) self.cursor_offset = 0 self.current_line = '' From 2ebb430d50ea5df1e050c0a58156e965ad3de8b6 Mon Sep 17 00:00:00 2001 From: SusInMotion Date: Mon, 13 Oct 2014 19:11:04 -0400 Subject: [PATCH 0071/1650] Inconsistent history message only shows up once --- bpython/curtsiesfrontend/repl.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f6fd408b7..30f2c0efa 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -309,7 +309,7 @@ def smarter_request_reload(desc): self.request_paint_to_clear_screen = False # next paint should clear screen self.inconsistent_history = False # offscreen command yields different result from history - self.history_messed_up = True # history error message displayed + self.history_messed_up = False # history error message displayed self.last_events = [None] * 50 # some commands act differently based on the prev event # this list doesn't include instances of event.Event, # only keypress-type events (no refresh screen events etc.) @@ -1028,8 +1028,8 @@ def paint(self, about_to_exit=False, user_quit=False): arr[:history.height,:history.width] = history else: - if self.inconsistent_history == True: - self.inconsistent_history = False + if self.inconsistent_history == True and self.history_messed_up == False: + self.history_messed_up = True #if INCONSISTENT_HISTORY_MSG not in self.display_lines: logger.debug(INCONSISTENT_HISTORY_MSG) msg = INCONSISTENT_HISTORY_MSG @@ -1037,7 +1037,7 @@ def paint(self, about_to_exit=False, user_quit=False): # self.scroll_offset -= 1 current_line_start_row = len(self.lines_for_display )- max(-1, self.scroll_offset) - + self.inconsistent_history = False if current_line_start_row < 0: #if current line trying to be drawn off the top of the screen logger.debug(CONTIGUITY_BROKEN_MSG) msg = CONTIGUITY_BROKEN_MSG @@ -1268,7 +1268,7 @@ def reevaluate(self, insert_into_history=False): old_display_lines_offscreen = old_display_lines[:len(self.display_lines) - num_lines_onscreen] display_lines_offscreen = self.display_lines[:-num_lines_onscreen] - if old_display_lines_offscreen != display_lines_offscreen: + if old_display_lines_offscreen != display_lines_offscreen and self.history_messed_up == False: self.scroll_offset=self.scroll_offset-(len(old_display_lines)-len(self.display_lines)) self.inconsistent_history = True From f4d353ce90d3c0d51db75a86ecafe66d4ebf0bf0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 14 Oct 2014 22:12:23 -0400 Subject: [PATCH 0072/1650] Add tests for special rewind cases --- bpython/curtsiesfrontend/repl.py | 4 +- bpython/test/test_curtsies_painting.py | 150 ++++++++++++++++++++++++- 2 files changed, 150 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 30f2c0efa..7837316ba 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -51,7 +51,9 @@ logger = logging.getLogger(__name__) -HELP_MESSAGE = """ +INCONSISTENT_HISTORY_MSG = u"#<---History inconsistent with output shown--->" +CONTIGUITY_BROKEN_MSG = u"#<---History contiguity broken by rewind--->" +HELP_MESSAGE = u""" Thanks for using bpython! See http://bpython-interpreter.org/ for info, http://docs.bpython-interpreter.org/ for docs, and https://github.com/bpython/bpython for source. diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index b27693a93..e67d51cbf 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -2,14 +2,22 @@ import unittest import sys import os +from contextlib import contextmanager -from curtsies.formatstringarray import FormatStringTest, fsarray +try: + from unittest import skip +except ImportError: + def skip(f): + return lambda self: None +from curtsies.formatstringarray import FormatStringTest, fsarray from curtsies.fmtfuncs import * +from curtsies.events import RefreshRequestEvent from bpython import config from bpython.curtsiesfrontend.repl import Repl from bpython.repl import History +from bpython.curtsiesfrontend.repl import INCONSISTENT_HISTORY_MSG, CONTIGUITY_BROKEN_MSG def setup_config(): config_struct = config.Struct() @@ -30,6 +38,9 @@ def assert_paint(self, screen, cursor_row_col): def assert_paint_ignoring_formatting(self, screen, cursor_row_col): array, cursor_pos = self.repl.paint() self.assertFSArraysEqualIgnoringFormatting(array, screen) + self.assertEqual(cursor_pos, cursor_row_col) + +class TestCurtsiesPaintingSimple(TestCurtsiesPainting): def test_startup(self): screen = fsarray([cyan('>>> '), cyan('Welcome to')]) @@ -48,7 +59,7 @@ def test_run_line(self): [self.repl.add_normal_character(c) for c in '1 + 1'] self.repl.on_enter() screen = fsarray([u'>>> 1 + 1', '2', 'Welcome to']) - self.assert_paint_ignoring_formatting(screen, (0, 9)) + self.assert_paint_ignoring_formatting(screen, (1, 1)) finally: sys.stdout = orig_stdout @@ -62,4 +73,137 @@ def test_completion(self): u'└───────────────────────┘', u'', u'Welcome to bpython! Press f'] - self.assert_paint_ignoring_formatting(screen, (0, 9)) + self.assert_paint_ignoring_formatting(screen, (0, 4)) + +@contextmanager +def output_to_repl(repl): + old_out, old_err = sys.stdout, sys.stderr + try: + sys.stdout, sys.stderr = repl.stdout, repl.stderr + yield + finally: + sys.stdout, sys.stderr = old_out, old_err + +class TestCurtsiesRewindRedraw(TestCurtsiesPainting): + def refresh(self, when='now'): + self.refresh_requests.append(RefreshRequestEvent(when=when)) + + def send_refreshes(self): + while self.refresh_requests: + self.repl.process_event(self.refresh_requests.pop()) + + def enter(self, line=None): + """Enter a line of text, avoiding autocompletion windows + + autocomplete could still happen if the entered line has + autocompletion that would happen then, but intermediate + stages won't happen""" + if line is not None: + self.repl.current_line = line + with output_to_repl(self.repl): + self.repl.on_enter() + self.send_refreshes() + + def undo(self): + with output_to_repl(self.repl): + self.repl.undo() + self.send_refreshes() + + def setUp(self): + self.refresh_requests = [] + self.repl = Repl(banner='', config=setup_config(), request_refresh=self.refresh) + self.repl.rl_history = History() # clear history + self.repl.height, self.repl.width = (5, 32) + + def test_rewind(self): + self.repl.current_line = '1 + 1' + self.enter() + screen = [u'>>> 1 + 1', + u'2', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (2, 4)) + self.repl.undo() + screen = [u'>>> '] + self.assert_paint_ignoring_formatting(screen, (0, 4)) + + @skip('wrong message') + def test_rewind_contiguity_loss(self): + self.enter('1 + 1') + self.enter('2 + 2') + self.enter('def foo(x):') + self.repl.current_line = ' return x + 1' + screen = [u'>>> 1 + 1', + u'2', + u'>>> 2 + 2', + u'4', + u'>>> def foo(x):', + u'... return x + 1'] + self.assert_paint_ignoring_formatting(screen, (5, 8)) + self.repl.scroll_offset = 1 + self.assert_paint_ignoring_formatting(screen[1:], (4, 8)) + self.undo() + screen = [u'2', + u'>>> 2 + 2', + u'4', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (3, 4)) + self.undo() + screen = [u'2', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (1, 4)) + self.undo() + screen = [CONTIGUITY_BROKEN_MSG[:self.repl.width], + u'>>> ', + u'', + u'', + u'', + u' '] #TODO why is that there? Necessary? + self.assert_paint_ignoring_formatting(screen, (1, 4)) + screen = [u'>>> '] + self.assert_paint_ignoring_formatting(screen, (0, 4)) + + def test_inconsistent_history_doesnt_happen_if_onscreen(self): + self.enter("1 + 1") + screen = [u">>> 1 + 1", + u'2', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (2, 4)) + self.enter("2 + 2") + screen = [u">>> 1 + 1", + u'2', + u'>>> 2 + 2', + u'4', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (4, 4)) + self.repl.display_lines[0] = self.repl.display_lines[0] * 2 + self.undo() + screen = [u">>> 1 + 1", + u'2', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (2, 4)) + + @skip('why is everything moved up?') + def test_rewind_inconsistent_history(self): + self.enter("1 + 1") + self.enter("2 + 2") + self.enter("3 + 3") + screen = [u">>> 1 + 1", + u'2', + u'>>> 2 + 2', + u'4', + u'>>> 3 + 3', + u'6', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (6, 4)) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[2:], (4, 4)) + self.repl.display_lines[0] = self.repl.display_lines[0] * 2 + self.undo() + screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], + u'>>> 2 + 2', + u'4', + u'>>> ', + u'', + u''] + self.assert_paint_ignoring_formatting(screen, (5, 4)) + From de56af5537aa74c527dc5ea4e416f8d811b1f2b2 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 14 Oct 2014 22:42:32 -0400 Subject: [PATCH 0073/1650] tests for clearing screen --- bpython/test/test_curtsies_painting.py | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index e67d51cbf..6e903384f 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -207,3 +207,44 @@ def test_rewind_inconsistent_history(self): u''] self.assert_paint_ignoring_formatting(screen, (5, 4)) + def test_clear_screen(self): + self.enter("1 + 1") + self.enter("2 + 2") + screen = [u">>> 1 + 1", + u'2', + u'>>> 2 + 2', + u'4', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (4, 4)) + self.repl.request_paint_to_clear_screen = True + screen = [u">>> 1 + 1", + u'2', + u'>>> 2 + 2', + u'4', + u'>>> ', u'', u'', u'', u''] + self.assert_paint_ignoring_formatting(screen, (4, 4)) + + @skip('the screen moved up again!') + def test_clear_screen_while_banner_visible(self): + self.repl.status_bar.message('STATUS_BAR') + self.enter("1 + 1") + self.enter("2 + 2") + screen = [u">>> 1 + 1", + u'2', + u'>>> 2 + 2', + u'4', + u'>>> ', + u'STATUS_BAR '] + self.assert_paint_ignoring_formatting(screen, (4, 4)) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[1:], (3, 4)) + + self.repl.request_paint_to_clear_screen = True + screen = [u">>> 1 + 1", + u'2', + u'>>> 2 + 2', + u'4', + u'>>> ', + u'', u'', u'', + u'STATUS_BAR '] + self.assert_paint_ignoring_formatting(screen, (0, 4)) From 9d487336bf9ba1186a1d61bc0605b23263235f47 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 14 Oct 2014 23:16:00 -0400 Subject: [PATCH 0074/1650] Remove curtsies fill_terminal option At some point I thought character-perfect matching of bpython.cli was important, but if folks want that UI, use cli! --- bpython/config.py | 2 -- bpython/curtsiesfrontend/repl.py | 34 +++++---------------- doc/sphinx/source/configuration-options.rst | 7 ----- 3 files changed, 7 insertions(+), 36 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 9aa01cf86..3de387c37 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -89,7 +89,6 @@ def loadini(struct, configfile): }, 'curtsies': { 'list_above' : False, - 'fill_terminal' : False, 'right_arrow_completion' : True, }}) if not config.read(config_path): @@ -157,7 +156,6 @@ def loadini(struct, configfile): struct.save_append_py = config.getboolean('general', 'save_append_py') struct.curtsies_list_above = config.getboolean('curtsies', 'list_above') - struct.curtsies_fill_terminal = config.getboolean('curtsies', 'fill_terminal') struct.curtsies_right_arrow_completion = config.getboolean('curtsies', 'right_arrow_completion') color_scheme_name = config.get('general', 'color_scheme') diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 7837316ba..5930f1f42 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -272,12 +272,7 @@ def smarter_request_reload(desc): self.get_term_hw = get_term_hw self.get_cursor_vertical_diff = get_cursor_vertical_diff - self.status_bar = StatusBar( - (_(" <%s> Rewind <%s> Save <%s> Pastebin <%s> Editor") - % (config.undo_key, config.save_key, config.pastebin_key, config.external_editor_key) - if config.curtsies_fill_terminal else ''), - refresh_request=self.request_refresh - ) + self.status_bar = StatusBar('', refresh_request=self.request_refresh) self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch) logger.debug("starting parent init") super(Repl, self).__init__(interp, config) @@ -1009,7 +1004,7 @@ def paint(self, about_to_exit=False, user_quit=False): self.clean_up_current_line_for_exit() # exception to not changing state! width, min_height = self.width, self.height - show_status_bar = bool(self.status_bar.should_show_message) or (self.config.curtsies_fill_terminal or self.status_bar.has_focus) + show_status_bar = bool(self.status_bar.should_show_message) or self.status_bar.has_focus if show_status_bar: min_height -= 1 @@ -1017,10 +1012,7 @@ def paint(self, about_to_exit=False, user_quit=False): #current_line_start_row = len(self.lines_for_display) - self.scroll_offset if self.request_paint_to_clear_screen: # or show_status_bar and about_to_exit ? self.request_paint_to_clear_screen = False - if self.config.curtsies_fill_terminal: #TODO clean up this logic - really necessary check? - arr = FSArray(self.height - 1 + current_line_start_row, width) - else: - arr = FSArray(self.height + current_line_start_row, width) + arr = FSArray(min_height + current_line_start_row, width) else: arr = FSArray(0, width) #TODO test case of current line filling up the whole screen (there aren't enough rows to show it) @@ -1106,23 +1098,11 @@ def paint(self, about_to_exit=False, user_quit=False): logger.debug('about to exit: %r', about_to_exit) if show_status_bar: - if self.config.curtsies_fill_terminal: - if about_to_exit: - arr[max(arr.height, min_height), :] = FSArray(1, width) - else: - arr[max(arr.height, min_height), :] = paint.paint_statusbar(1, width, self.status_bar.current_line, self.config) - - if self.presentation_mode: - rows = arr.height - columns = arr.width - last_key_box = paint.paint_last_events(rows, columns, [events.pp_event(x) for x in self.last_events if x]) - arr[arr.height-last_key_box.height:arr.height, arr.width-last_key_box.width:arr.width] = last_key_box + statusbar_row = min_height + 1 if arr.height == min_height else arr.height + if about_to_exit: + arr[statusbar_row, :] = FSArray(1, width) else: - statusbar_row = min_height + 1 if arr.height == min_height else arr.height - if about_to_exit: - arr[statusbar_row, :] = FSArray(1, width) - else: - arr[statusbar_row, :] = paint.paint_statusbar(1, width, self.status_bar.current_line, self.config) + arr[statusbar_row, :] = paint.paint_statusbar(1, width, self.status_bar.current_line, self.config) if self.config.color_scheme['background'] not in ('d', 'D'): for r in range(arr.height): diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 1aa79b4af..34b8d5da7 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -290,13 +290,6 @@ This refers to the ``[curtsies]`` section in your config file. .. versionadded:: 0.13 -fill_terminal -^^^^^^^^^^^^^ -Default: False - -Whether bpython should clear the screen on start, and always display a status -bar at the bottom. - list_above ^^^^^^^^^^ Default: False From 313189c017a66effe494f23b6a291d578ac5cdaa Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 14 Oct 2014 23:25:40 -0400 Subject: [PATCH 0075/1650] tests for clearing, fix issue #343 --- bpython/curtsiesfrontend/repl.py | 6 ++++-- bpython/test/test_curtsies_painting.py | 24 +++++++++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 5930f1f42..df1075fab 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1006,9 +1006,11 @@ def paint(self, about_to_exit=False, user_quit=False): width, min_height = self.width, self.height show_status_bar = bool(self.status_bar.should_show_message) or self.status_bar.has_focus if show_status_bar: - min_height -= 1 + min_height -= 1 # because we're going to tack the status bar on at the end, + # shoot for an array one less than the height of the screen current_line_start_row = len(self.lines_for_display) - max(0, self.scroll_offset) + #TODO how is the situation of self.scroll_offset < 0 possible? #current_line_start_row = len(self.lines_for_display) - self.scroll_offset if self.request_paint_to_clear_screen: # or show_status_bar and about_to_exit ? self.request_paint_to_clear_screen = False @@ -1098,7 +1100,7 @@ def paint(self, about_to_exit=False, user_quit=False): logger.debug('about to exit: %r', about_to_exit) if show_status_bar: - statusbar_row = min_height + 1 if arr.height == min_height else arr.height + statusbar_row = min_height if arr.height == min_height else arr.height if about_to_exit: arr[statusbar_row, :] = FSArray(1, width) else: diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 6e903384f..a41ce2f17 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -71,7 +71,6 @@ def test_completion(self): u'┌───────────────────────┐', u'│ set( setattr( │', u'└───────────────────────┘', - u'', u'Welcome to bpython! Press f'] self.assert_paint_ignoring_formatting(screen, (0, 4)) @@ -126,7 +125,6 @@ def test_rewind(self): screen = [u'>>> '] self.assert_paint_ignoring_formatting(screen, (0, 4)) - @skip('wrong message') def test_rewind_contiguity_loss(self): self.enter('1 + 1') self.enter('2 + 2') @@ -182,7 +180,7 @@ def test_inconsistent_history_doesnt_happen_if_onscreen(self): u'>>> '] self.assert_paint_ignoring_formatting(screen, (2, 4)) - @skip('why is everything moved up?') + @skip('for inconsistent history check') def test_rewind_inconsistent_history(self): self.enter("1 + 1") self.enter("2 + 2") @@ -224,8 +222,7 @@ def test_clear_screen(self): u'>>> ', u'', u'', u'', u''] self.assert_paint_ignoring_formatting(screen, (4, 4)) - @skip('the screen moved up again!') - def test_clear_screen_while_banner_visible(self): + def test_scroll_down_while_banner_visible(self): self.repl.status_bar.message('STATUS_BAR') self.enter("1 + 1") self.enter("2 + 2") @@ -239,12 +236,25 @@ def test_clear_screen_while_banner_visible(self): self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:], (3, 4)) - self.repl.request_paint_to_clear_screen = True + def test_clear_screen_while_banner_visible(self): + self.repl.status_bar.message('STATUS_BAR') + self.enter("1 + 1") + self.enter("2 + 2") screen = [u">>> 1 + 1", u'2', + u'>>> 2 + 2', + u'4', + u'>>> ', + u'STATUS_BAR '] + self.assert_paint_ignoring_formatting(screen, (4, 4)) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[1:], (3, 4)) + + self.repl.request_paint_to_clear_screen = True + screen = [u'2', u'>>> 2 + 2', u'4', u'>>> ', u'', u'', u'', u'STATUS_BAR '] - self.assert_paint_ignoring_formatting(screen, (0, 4)) + self.assert_paint_ignoring_formatting(screen, (3, 4)) From 10948f8f8b19bf54c22e1b478d4052fcf78475f6 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 15 Oct 2014 08:52:01 -0400 Subject: [PATCH 0076/1650] test_rewind_contiguity_loss test passing --- bpython/curtsiesfrontend/repl.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index df1075fab..e3b6b8aef 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1018,7 +1018,7 @@ def paint(self, about_to_exit=False, user_quit=False): else: arr = FSArray(0, width) #TODO test case of current line filling up the whole screen (there aren't enough rows to show it) - if self.inconsistent_history == False & current_line_start_row >= 0: + if self.inconsistent_history == False and current_line_start_row >= 0: logger.debug("start %i",current_line_start_row) history = paint.paint_history(current_line_start_row, width, self.lines_for_display) arr[:history.height,:history.width] = history @@ -1031,7 +1031,7 @@ def paint(self, about_to_exit=False, user_quit=False): msg = INCONSISTENT_HISTORY_MSG arr[0, 0:min(len(msg), width)] = [msg[:width]] # self.scroll_offset -= 1 - + current_line_start_row = len(self.lines_for_display )- max(-1, self.scroll_offset) self.inconsistent_history = False if current_line_start_row < 0: #if current line trying to be drawn off the top of the screen @@ -1244,19 +1244,16 @@ def reevaluate(self, insert_into_history=False): self.process_event(events.RefreshRequestEvent()) sys.stdin = self.stdin self.reevaluating = False - - num_lines_onscreen = len(self.lines_for_display) - max(0, self.scroll_offset) - old_display_lines_offscreen = [] - display_lines_offscreen = [] - if old_display_lines[:len(self.display_lines) - num_lines_onscreen]!= self.display_lines: - old_display_lines_offscreen = old_display_lines[:len(self.display_lines) - num_lines_onscreen] - display_lines_offscreen = self.display_lines[:-num_lines_onscreen] - - if old_display_lines_offscreen != display_lines_offscreen and self.history_messed_up == False: - self.scroll_offset=self.scroll_offset-(len(old_display_lines)-len(self.display_lines)) + num_lines_onscreen = len(self.lines_for_display) - max(0, self.scroll_offset) + display_lines_offscreen = self.display_lines[:len(self.display_lines) - num_lines_onscreen] + old_display_lines_offscreen = old_display_lines[:len(self.display_lines) - num_lines_onscreen] + logger.debug('old_display_lines_offscreen %r', old_display_lines_offscreen) + logger.debug('display_lines_offscreen %r', display_lines_offscreen) + if old_display_lines_offscreen[:len(display_lines_offscreen)] != display_lines_offscreen and self.history_messed_up == False: + self.scroll_offset = self.scroll_offset - (len(old_display_lines)-len(self.display_lines)) self.inconsistent_history = True - #self.scroll_offset = self.scroll_offset - max(-1,(len(old_display_lines_offscreen)-len(display_lines_offscreen)+1)) + logger.debug('after rewind, self.inconsistent_history is %r', self.inconsistent_history) self.cursor_offset = 0 self.current_line = '' From 82b39bf4d887949fe88f328376011af4c195d50d Mon Sep 17 00:00:00 2001 From: lrraymond13 Date: Tue, 14 Oct 2014 18:48:43 -0400 Subject: [PATCH 0077/1650] Fixed #284 --- bpython/args.py | 2 ++ bpython/test/test_args.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 bpython/test/test_args.py diff --git a/bpython/args.py b/bpython/args.py index ff026357b..bf9f32603 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -90,6 +90,7 @@ def parse(args, extras=None, ignore_stdin=False): if not ignore_stdin and not (sys.stdin.isatty() and sys.stdout.isatty()): interpreter = code.InteractiveInterpreter() + print "Entering st.read %s" % sys.stdout.isatty() interpreter.runsource(sys.stdin.read()) raise SystemExit @@ -107,5 +108,6 @@ def exec_code(interpreter, args): source = sourcefile.read() old_argv, sys.argv = sys.argv, args sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) + interpreter.locals['__file__'] = args[0] interpreter.runsource(source, args[0], 'exec') sys.argv = old_argv diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py new file mode 100644 index 000000000..51b1eb651 --- /dev/null +++ b/bpython/test/test_args.py @@ -0,0 +1,20 @@ +import unittest +import subprocess +import tempfile +import sys + + + + +class TestExecArgs(unittest.TestCase): + def test_exec_dunder_file(self): + with tempfile.NamedTemporaryFile(delete=False) as f: + f.write( + "import sys; print 'hello'; sys.stderr.write(__file__); sys.stderr.flush();") + f.flush() + print open(f.name).read() + p = subprocess.Popen(['bpython-curtsies', f.name], stderr= subprocess.PIPE) + + self.assertEquals(p.stderr.read().strip(), f.name) + + From 3fa21ab77927347587974cd312254bcc1cc33889 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 16 Oct 2014 11:29:11 -0400 Subject: [PATCH 0078/1650] fix Python 3 test --- bpython/test/test_args.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 51b1eb651..e14bf1afe 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -10,11 +10,11 @@ class TestExecArgs(unittest.TestCase): def test_exec_dunder_file(self): with tempfile.NamedTemporaryFile(delete=False) as f: f.write( - "import sys; print 'hello'; sys.stderr.write(__file__); sys.stderr.flush();") + "import sys; sys.stderr.write(__file__); sys.stderr.flush();".encode('ascii')) f.flush() print open(f.name).read() - p = subprocess.Popen(['bpython-curtsies', f.name], stderr= subprocess.PIPE) - - self.assertEquals(p.stderr.read().strip(), f.name) + p = subprocess.Popen(['bpython-curtsies', f.name], stderr=subprocess.PIPE) + + self.assertEquals(p.stderr.read().strip().decode('ascii'), f.name) From 6469adf5b3d95c2dbdeda4c7b59af8d40e0aa785 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 16 Oct 2014 11:03:04 -0400 Subject: [PATCH 0079/1650] Tests for all sorts of inconsistent history edge cases Also fixes #371 and #375 bumps curtsies version for better testing functions --- bpython/curtsiesfrontend/repl.py | 77 ++++++---- bpython/test/test_curtsies_painting.py | 198 ++++++++++++++++++++++++- setup.py | 2 +- 3 files changed, 242 insertions(+), 35 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index e3b6b8aef..982796ae1 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -306,7 +306,7 @@ def smarter_request_reload(desc): self.request_paint_to_clear_screen = False # next paint should clear screen self.inconsistent_history = False # offscreen command yields different result from history - self.history_messed_up = False # history error message displayed + self.history_already_messed_up = False # history error message displayed self.last_events = [None] * 50 # some commands act differently based on the prev event # this list doesn't include instances of event.Event, # only keypress-type events (no refresh screen events etc.) @@ -1018,31 +1018,27 @@ def paint(self, about_to_exit=False, user_quit=False): else: arr = FSArray(0, width) #TODO test case of current line filling up the whole screen (there aren't enough rows to show it) - if self.inconsistent_history == False and current_line_start_row >= 0: - logger.debug("start %i",current_line_start_row) - history = paint.paint_history(current_line_start_row, width, self.lines_for_display) - arr[:history.height,:history.width] = history - - else: - if self.inconsistent_history == True and self.history_messed_up == False: - self.history_messed_up = True - #if INCONSISTENT_HISTORY_MSG not in self.display_lines: - logger.debug(INCONSISTENT_HISTORY_MSG) - msg = INCONSISTENT_HISTORY_MSG - arr[0, 0:min(len(msg), width)] = [msg[:width]] - # self.scroll_offset -= 1 - - current_line_start_row = len(self.lines_for_display )- max(-1, self.scroll_offset) - self.inconsistent_history = False - if current_line_start_row < 0: #if current line trying to be drawn off the top of the screen - logger.debug(CONTIGUITY_BROKEN_MSG) - msg = CONTIGUITY_BROKEN_MSG - arr[0, 0:min(len(msg), width)] = [msg[:width]] + def move_screen_up(current_line_start_row): # move screen back up a screen minus a line - while current_line_start_row < 0: - self.scroll_offset = self.scroll_offset - self.height - current_line_start_row = len(self.lines_for_display) - max(-1, self.scroll_offset) + while current_line_start_row < 0: + logger.debug('scroll_offset was %s, current_line_start_row was %s', self.scroll_offset, current_line_start_row) + self.scroll_offset = self.scroll_offset - self.height + current_line_start_row = len(self.lines_for_display) - max(-1, self.scroll_offset) + logger.debug('scroll_offset changed to %s, current_line_start_row changed to %s', self.scroll_offset, current_line_start_row) + return current_line_start_row + + if self.inconsistent_history == True and not self.history_already_messed_up: + logger.debug(INCONSISTENT_HISTORY_MSG) + self.history_already_messed_up = True + msg = INCONSISTENT_HISTORY_MSG + arr[0, 0:min(len(msg), width)] = [msg[:width]] + current_line_start_row += 1 # for the message + self.scroll_offset -= 1 # to make up for the scroll we're going to receive + # after we render scrolls down a line + + current_line_start_row = move_screen_up(current_line_start_row) + logger.debug('current_line_start_row: %r', current_line_start_row) history = paint.paint_history(max(0, current_line_start_row - 1), width, self.lines_for_display) arr[1:history.height+1,:history.width] = history @@ -1050,6 +1046,29 @@ def paint(self, about_to_exit=False, user_quit=False): if arr.height <= min_height: arr[min_height, 0] = ' ' # force scroll down to hide broken history message + elif current_line_start_row < 0: #if current line trying to be drawn off the top of the screen + logger.debug(CONTIGUITY_BROKEN_MSG) + msg = CONTIGUITY_BROKEN_MSG + arr[0, 0:min(len(msg), width)] = [msg[:width]] + + current_line_start_row = move_screen_up(current_line_start_row) + + history = paint.paint_history(max(0, current_line_start_row - 1), width, self.lines_for_display) + arr[1:history.height+1,:history.width] = history + + if arr.height <= min_height: + arr[min_height, 0] = ' ' # force scroll down to hide broken history message + + else: + assert current_line_start_row >= 0 + logger.debug("no history issues. start %i",current_line_start_row) + history = paint.paint_history(current_line_start_row, width, self.lines_for_display) + arr[:history.height,:history.width] = history + + + + + self.inconsistent_history = False current_line = paint.paint_current_line(min_height, width, self.current_cursor_line) if user_quit: # quit() or exit() in interp @@ -1077,7 +1096,7 @@ def paint(self, about_to_exit=False, user_quit=False): assert cursor_column >= 0, (cursor_column, len(self.current_cursor_line), len(self.current_line), self.cursor_offset) cursor_row += current_line_start_row - if self.list_win_visible: + if self.list_win_visible and not self.coderunner.running: logger.debug('infobox display code running') visible_space_above = history.height visible_space_below = min_height - current_line_end_row - 1 @@ -1248,10 +1267,10 @@ def reevaluate(self, insert_into_history=False): num_lines_onscreen = len(self.lines_for_display) - max(0, self.scroll_offset) display_lines_offscreen = self.display_lines[:len(self.display_lines) - num_lines_onscreen] old_display_lines_offscreen = old_display_lines[:len(self.display_lines) - num_lines_onscreen] - logger.debug('old_display_lines_offscreen %r', old_display_lines_offscreen) - logger.debug('display_lines_offscreen %r', display_lines_offscreen) - if old_display_lines_offscreen[:len(display_lines_offscreen)] != display_lines_offscreen and self.history_messed_up == False: - self.scroll_offset = self.scroll_offset - (len(old_display_lines)-len(self.display_lines)) + logger.debug('old_display_lines_offscreen %s', '|'.join([str(x) for x in old_display_lines_offscreen])) + logger.debug(' display_lines_offscreen %s', '|'.join([str(x) for x in display_lines_offscreen])) + if old_display_lines_offscreen[:len(display_lines_offscreen)] != display_lines_offscreen and not self.history_already_messed_up: + #self.scroll_offset = self.scroll_offset + (len(old_display_lines)-len(self.display_lines)) self.inconsistent_history = True logger.debug('after rewind, self.inconsistent_history is %r', self.inconsistent_history) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index a41ce2f17..6013a6ce8 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -35,10 +35,11 @@ def assert_paint(self, screen, cursor_row_col): self.assertFSArraysEqual(array, screen) self.assertEqual(cursor_pos, cursor_row_col) - def assert_paint_ignoring_formatting(self, screen, cursor_row_col): + def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None): array, cursor_pos = self.repl.paint() self.assertFSArraysEqualIgnoringFormatting(array, screen) - self.assertEqual(cursor_pos, cursor_row_col) + if cursor_row_col is not None: + self.assertEqual(cursor_pos, cursor_row_col) class TestCurtsiesPaintingSimple(TestCurtsiesPainting): @@ -180,7 +181,6 @@ def test_inconsistent_history_doesnt_happen_if_onscreen(self): u'>>> '] self.assert_paint_ignoring_formatting(screen, (2, 4)) - @skip('for inconsistent history check') def test_rewind_inconsistent_history(self): self.enter("1 + 1") self.enter("2 + 2") @@ -202,8 +202,172 @@ def test_rewind_inconsistent_history(self): u'4', u'>>> ', u'', - u''] - self.assert_paint_ignoring_formatting(screen, (5, 4)) + u' '] + self.assert_paint_ignoring_formatting(screen, (3, 4)) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4)) + self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4)) + + def test_rewind_inconsistent_history_more_lines_same_screen(self): + self.repl.width = 60 + sys.a = 5 + self.enter("import sys") + self.enter("for i in range(sys.a): print(sys.a)") + self.enter() + self.enter("1 + 1") + self.enter("2 + 2") + screen = [u">>> import sys", + u">>> for i in range(sys.a): print(sys.a)", + u'... ', + u'5', + u'5', + u'5', + u'5', + u'5', + u'>>> 1 + 1', + u'2', + u'>>> 2 + 2', + u'4', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (12, 4)) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) + sys.a = 6 + self.undo() + screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], + u'6', + u'>>> 1 + 1', # everything will jump down a line - that's perfectly reasonable + u'2', + u'>>> ', + u' '] + self.assert_paint_ignoring_formatting(screen, (4, 4)) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[1:-1], (3, 4)) + + def test_rewind_inconsistent_history_more_lines_lower_screen(self): + self.repl.width = 60 + sys.a = 5 + self.enter("import sys") + self.enter("for i in range(sys.a): print(sys.a)") + self.enter() + self.enter("1 + 1") + self.enter("2 + 2") + screen = [u">>> import sys", + u">>> for i in range(sys.a): print(sys.a)", + u'... ', + u'5', + u'5', + u'5', + u'5', + u'5', + u'>>> 1 + 1', + u'2', + u'>>> 2 + 2', + u'4', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (12, 4)) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) + sys.a = 8 + self.undo() + screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], + u'8', + u'8', + u'8', + u'>>> 1 + 1', + u'2', + u'>>> '] + self.assert_paint_ignoring_formatting(screen) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[-5:]) + + def test_rewind_inconsistent_history_more_lines_raise_screen(self): + self.repl.width = 60 + sys.a = 5 + self.enter("import sys") + self.enter("for i in range(sys.a): print(sys.a)") + self.enter() + self.enter("1 + 1") + self.enter("2 + 2") + screen = [u">>> import sys", + u">>> for i in range(sys.a): print(sys.a)", + u'... ', + u'5', + u'5', + u'5', + u'5', + u'5', + u'>>> 1 + 1', + u'2', + u'>>> 2 + 2', + u'4', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (12, 4)) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) + sys.a = 1 + self.undo() + screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], + u'1', + u'>>> 1 + 1', + u'2', + u'>>> ', + u' '] + self.assert_paint_ignoring_formatting(screen) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[1:-1]) + + def test_rewind_history_not_quite_inconsistent(self): + self.repl.width = 50 + sys.a = 5 + self.enter("for i in range(__import__('sys').a): print(i)") + self.enter() + self.enter("1 + 1") + self.enter("2 + 2") + screen = [u">>> for i in range(__import__('sys').a): print(i)", + u'... ', + u'0', + u'1', + u'2', + u'3', + u'4', + u'>>> 1 + 1', + u'2', + u'>>> 2 + 2', + u'4', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (11, 4)) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[7:], (4, 4)) + sys.a = 6 + self.undo() + screen = [u'5', + u'>>> 1 + 1', # everything will jump down a line - that's perfectly reasonable + u'2', + u'>>> ',] + self.assert_paint_ignoring_formatting(screen, (3, 4)) + + def test_rewind_barely_consistent(self): + self.enter("1 + 1") + self.enter("2 + 2") + self.enter("3 + 3") + screen = [u">>> 1 + 1", + u'2', + u'>>> 2 + 2', + u'4', + u'>>> 3 + 3', + u'6', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (6, 4)) + self.repl.scroll_offset += len(screen) - self.repl.height + self.assert_paint_ignoring_formatting(screen[2:], (4, 4)) + self.repl.display_lines[2] = self.repl.display_lines[2] * 2 + self.undo() + screen = [u'>>> 2 + 2', + u'4', + u'>>> '] + self.assert_paint_ignoring_formatting(screen, (2, 4)) + def test_clear_screen(self): self.enter("1 + 1") @@ -258,3 +422,27 @@ def test_clear_screen_while_banner_visible(self): u'', u'', u'', u'STATUS_BAR '] self.assert_paint_ignoring_formatting(screen, (3, 4)) + + def test_cursor_stays_at_bottom_of_screen(self): + """infobox showing up during intermediate render was causing this to fail, #371""" + self.repl.width = 50 + self.repl.current_line = "__import__('random').__name__" + with output_to_repl(self.repl): + self.repl.on_enter() + screen = [u">>> __import__('random').__name__", + u"'random'"] + self.assert_paint_ignoring_formatting(screen) + + with output_to_repl(self.repl): + self.repl.process_event(self.refresh_requests.pop()) + screen = [u">>> __import__('random').__name__", + u"'random'", + u""] + self.assert_paint_ignoring_formatting(screen) + + with output_to_repl(self.repl): + self.repl.process_event(self.refresh_requests.pop()) + screen = [u">>> __import__('random').__name__", + u"'random'", + u">>> "] + self.assert_paint_ignoring_formatting(screen, (2, 4)) diff --git a/setup.py b/setup.py index 8de734e8c..f43f589dd 100755 --- a/setup.py +++ b/setup.py @@ -151,7 +151,7 @@ def initialize_options(self): if sys.version_info[:2] >= (2, 6): # curtsies only supports 2.6 and onwards - extras_require['curtsies'] = ['curtsies >=0.1.7, <0.2.0', 'greenlet'] + extras_require['curtsies'] = ['curtsies >=0.1.11, <0.2.0', 'greenlet'] extras_require['watch'] = ['watchdog'] packages.append("bpython.curtsiesfrontend") entry_points['console_scripts'].append( From 4ae4c81ebaab8007236a868801cdd1d1899c89b1 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 16 Oct 2014 23:16:42 -0400 Subject: [PATCH 0080/1650] first pass at python version banner --- bpython/args.py | 4 ++++ bpython/curtsies.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bpython/args.py b/bpython/args.py index bf9f32603..8f8e8a5be 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -22,6 +22,10 @@ def error(self, msg): raise OptionParserFailed() +def version_banner(): + return 'bpython version %s on top of Python %s %s' % ( + __version__, sys.version.split()[0], sys.executable) + def parse(args, extras=None, ignore_stdin=False): """Receive an argument list - if None, use sys.argv - parse all args and take appropriate action. Also receive optional extra options: this should diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 0657c0115..4e6bf3e17 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -61,7 +61,7 @@ def main(args=None, locals_=None, banner=None): else: sys.path.insert(0, '') # expected for interactive sessions (vanilla python does it) - + print(bpargs.version_banner()) mainloop(config, locals_, banner, interp, paste, interactive=(not exec_args)) def mainloop(config, locals_, banner, interp=None, paste=None, interactive=True): From 8321deff122819a52bcb14cba4749665ba6cb349 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 16 Oct 2014 23:32:46 -0400 Subject: [PATCH 0081/1650] remove nop test in test_manual_readline --- bpython/test/test_manual_readline.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 8bb6eb0da..5cfdcc6c7 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -258,11 +258,6 @@ class config: att = 'c' self.assertEqual(configured_edits.call('c', cursor_offset=5, line='asfd'), ('hi', 2)) - def test_actual(self): - class config: att = 'c' - key_dispatch = {'c': 'c'} - configured_edits = self.edits.mapping_with_config(config, key_dispatch) - if __name__ == '__main__': unittest.main() From 663ea479c1bca96ec0d806246c794ff138153258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Fri, 17 Oct 2014 21:09:11 +0200 Subject: [PATCH 0082/1650] Remove debug prints. --- bpython/args.py | 1 - bpython/test/test_args.py | 1 - 2 files changed, 2 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 8f8e8a5be..0e97985ad 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -94,7 +94,6 @@ def parse(args, extras=None, ignore_stdin=False): if not ignore_stdin and not (sys.stdin.isatty() and sys.stdout.isatty()): interpreter = code.InteractiveInterpreter() - print "Entering st.read %s" % sys.stdout.isatty() interpreter.runsource(sys.stdin.read()) raise SystemExit diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index e14bf1afe..829081837 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -12,7 +12,6 @@ def test_exec_dunder_file(self): f.write( "import sys; sys.stderr.write(__file__); sys.stderr.flush();".encode('ascii')) f.flush() - print open(f.name).read() p = subprocess.Popen(['bpython-curtsies', f.name], stderr=subprocess.PIPE) self.assertEquals(p.stderr.read().strip().decode('ascii'), f.name) From d7afe7350654f609f344d4dd0f341a1ae0791118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Fri, 17 Oct 2014 21:32:24 +0200 Subject: [PATCH 0083/1650] Small (subjective) clean-up in tests. --- bpython/test/test_args.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 829081837..2c876847a 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -1,19 +1,22 @@ import unittest import subprocess -import tempfile import sys - - +import tempfile +from textwrap import dedent class TestExecArgs(unittest.TestCase): def test_exec_dunder_file(self): - with tempfile.NamedTemporaryFile(delete=False) as f: - f.write( - "import sys; sys.stderr.write(__file__); sys.stderr.flush();".encode('ascii')) + with tempfile.NamedTemporaryFile(delete=False, mode="w") as f: + f.write(dedent("""\ + import sys + sys.stderr.write(__file__) + sys.stderr.flush()""")) f.flush() - p = subprocess.Popen(['bpython-curtsies', f.name], stderr=subprocess.PIPE) - - self.assertEquals(p.stderr.read().strip().decode('ascii'), f.name) - + p = subprocess.Popen( + [sys.executable, "-m", "bpython.curtsies", f.name], + stderr=subprocess.PIPE, + universal_newlines=True) + (_, stderr) = p.communicate() + self.assertEquals(stderr.strip(), f.name) From 3995c1261c11bd137f526c3f53ada59f5094a8a1 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 17 Oct 2014 12:17:17 -0400 Subject: [PATCH 0084/1650] add failing test for issue #415 --- bpython/test/test_curtsies_repl.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index a83200607..f72bb2f67 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -1,3 +1,4 @@ +# coding: utf8 import code import os import sys @@ -51,6 +52,10 @@ def test_external_communication(self): self.repl.send_current_block_to_external_editor() self.repl.send_session_to_external_editor() + def test_external_communication_encoding(self): + self.repl.display_lines.append(u'>>> åß∂ƒ') + self.repl.send_session_to_external_editor() + def test_get_last_word(self): self.repl.rl_history.entries=['1','2 3','4 5 6'] self.repl._set_current_line('abcde') From 3cddcc61fce73c85b4b7141ca858b3ad5b8f714b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 17 Oct 2014 19:54:00 -0400 Subject: [PATCH 0085/1650] fix 415 --- bpython/curtsiesfrontend/interpreter.py | 9 ++++----- bpython/curtsiesfrontend/repl.py | 12 ++++++------ bpython/repl.py | 7 +++++-- bpython/test/test_curtsies_repl.py | 5 +++-- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 3be4a25f2..a030ea12a 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -36,16 +36,16 @@ class BPythonFormatter(Formatter): them into the appropriate format string as defined above, then writes to the outfile object the final formatted string. This does not write real strings. It writes format string (FmtStr) objects. - + See the Pygments source for more info; it's pretty straightforward.""" - + def __init__(self, color_scheme, **options): self.f_strings = {} for k, v in color_scheme.iteritems(): self.f_strings[k] = '\x01%s' % (v,) Formatter.__init__(self, **options) - + def format(self, tokensource, outfile): o = '' @@ -54,7 +54,7 @@ def format(self, tokensource, outfile): token = token.parent o += "%s\x03%s\x04" % (self.f_strings[token], text) outfile.write(parse(o.rstrip())) - + class Interp(code.InteractiveInterpreter): def __init__(self, locals=None): """Constructor. @@ -128,7 +128,6 @@ def showtraceback(self): lexer = get_lexer_by_name("pytb", stripall=True) self.format(tbtext,lexer) - def format(self, tbtext, lexer): traceback_informative_formatter = BPythonFormatter(default_colors) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2b5854055..54b89bca8 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -667,7 +667,7 @@ def process_simple_keypress(self, e): self.add_normal_character(e) def send_current_block_to_external_editor(self, filename=None): - text = self.send_to_external_editor(self.get_current_block().encode('utf8')) + text = self.send_to_external_editor(self.get_current_block()) lines = [line for line in text.split('\n')] while lines and not lines[-1].split(): lines.pop() @@ -679,11 +679,11 @@ def send_current_block_to_external_editor(self, filename=None): self.cursor_offset = len(self.current_line) def send_session_to_external_editor(self, filename=None): - for_editor = '### current bpython session - file will be reevaluated, ### lines will not be run\n'.encode('utf8') - for_editor += ('\n'.join(line[len(self.ps1):] if line.startswith(self.ps1) else - (line[len(self.ps2):] if line.startswith(self.ps2) else - '### '+line) - for line in self.getstdout().split('\n')).encode('utf8')) + for_editor = u'### current bpython session - file will be reevaluated, ### lines will not be run\n' + for_editor += '\n'.join(line[len(self.ps1):] if line.startswith(self.ps1) else + (line[len(self.ps2):] if line.startswith(self.ps2) else + '### '+line) + for line in self.getstdout().split('\n')) text = self.send_to_external_editor(for_editor) lines = text.split('\n') self.history = [line for line in lines if line[:4] != '### '] diff --git a/bpython/repl.py b/bpython/repl.py index 6abc07ec3..13c06fb5c 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1046,11 +1046,14 @@ def send_to_external_editor(self, text, filename=None): """Returns modified text from an editor, or the oriignal text if editor exited with non-zero""" editor_args = shlex.split(self.config.editor) with tempfile.NamedTemporaryFile(suffix='.py') as temp: - temp.write(text) + temp.write(text.encode(getpreferredencoding())) temp.flush() if subprocess.call(editor_args + [temp.name]) == 0: with open(temp.name) as f: - return f.read() + if py3: + return f.read() + else: + return f.read().decode(getpreferredencoding()) else: return text diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index f72bb2f67..00652fa78 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -53,8 +53,9 @@ def test_external_communication(self): self.repl.send_session_to_external_editor() def test_external_communication_encoding(self): - self.repl.display_lines.append(u'>>> åß∂ƒ') - self.repl.send_session_to_external_editor() + with captured_output(): + self.repl.display_lines.append(u'>>> "åß∂ƒ"') + self.repl.send_session_to_external_editor() def test_get_last_word(self): self.repl.rl_history.entries=['1','2 3','4 5 6'] From 970d3b9298295bf0218467e507675b9bf0037709 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 17 Oct 2014 20:11:53 -0400 Subject: [PATCH 0086/1650] just do module discovery once and skip urwid UrwidCrashersTest --- bpython/test/test_crashers.py | 7 +++++++ bpython/test/test_importcompletion.py | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 1b85e6916..533378ca1 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -7,6 +7,12 @@ import textwrap import unittest +try: + from unittest import skip +except ImportError: + def skip(f): + return lambda self: None + try: from twisted.internet import reactor from twisted.internet.defer import Deferred @@ -104,6 +110,7 @@ def check_no_traceback(self, data): class CursesCrashersTest(TrialTestCase, CrashersTest): backend = "cli" + @skip("take 6 seconds, and Simon says we can skip them") class UrwidCrashersTest(TrialTestCase, CrashersTest): backend = "urwid" diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 5124455eb..706be7bdb 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -15,11 +15,13 @@ def test_package_completion(self): class TestRealComplete(unittest.TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): [_ for _ in importcompletion.find_iterator] __import__('sys') __import__('os') - def tearDown(self): + @classmethod + def tearDownClass(cls): importcompletion.find_iterator = importcompletion.find_all_modules() importcompletion.modules = set() def test_from_attribute(self): From 8fbb11e1ec33a04d42ed11920d164f386982a47f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 17 Oct 2014 20:34:00 -0400 Subject: [PATCH 0087/1650] convert tabs in rl_history entries to spaces This will lead to duplicate history entries because the strings will no longer match, but it's an improvement --- bpython/curtsiesfrontend/repl.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 54b89bca8..fc37cf56f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -643,14 +643,16 @@ def yank_from_buffer(self): def up_one_line(self): self.rl_history.enter(self.current_line) - self._set_current_line(self.rl_history.back(False, search=self.config.curtsies_right_arrow_completion), - reset_rl_history=False) + self._set_current_line(tabs_to_spaces(self.rl_history.back(False, + search=self.config.curtsies_right_arrow_completion)), + reset_rl_history=False) self._set_cursor_offset(len(self.current_line), reset_rl_history=False) def down_one_line(self): self.rl_history.enter(self.current_line) - self._set_current_line(self.rl_history.forward(False, search=self.config.curtsies_right_arrow_completion), - reset_rl_history=False) + self._set_current_line(tabs_to_spaces(self.rl_history.forward(False, + search=self.config.curtsies_right_arrow_completion)), + reset_rl_history=False) self._set_cursor_offset(len(self.current_line), reset_rl_history=False) def process_simple_keypress(self, e): @@ -1369,6 +1371,9 @@ def key_help_text(self): def is_nop(char): return unicodedata.category(unicode(char)) == 'Cc' +def tabs_to_spaces(line): + return line.replace('\t', ' ') + def compress_paste_event(paste_event): """If all events in a paste event are identical and not simple characters, returns one of them From 9a90d60dd680c54f508dfea1af24e3247d164a9c Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 17 Oct 2014 23:16:54 -0400 Subject: [PATCH 0088/1650] Improve fish-style highlighted reverse search --- bpython/curtsiesfrontend/repl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index fc37cf56f..785f3d149 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -927,7 +927,7 @@ def current_line_formatted(self): if self.incremental_search_target in self.current_line: fs = fmtfuncs.on_magenta(self.incremental_search_target).join(fs.split(self.incremental_search_target)) elif self.rl_history.saved_line and self.rl_history.saved_line in self.current_line: - if self.config.curtsies_right_arrow_completion: + if self.config.curtsies_right_arrow_completion and self.rl_history.index != 0: fs = fmtfuncs.on_magenta(self.rl_history.saved_line).join(fs.split(self.rl_history.saved_line)) logger.debug('Display line %r -> %r', self.current_line, fs) else: @@ -1203,6 +1203,8 @@ def __repr__(self): def _get_current_line(self): return self._current_line def _set_current_line(self, line, update_completion=True, reset_rl_history=True, clear_special_mode=True): + if self._current_line == line: + return self._current_line = line if update_completion: self.update_completion() @@ -1214,7 +1216,7 @@ def _set_current_line(self, line, update_completion=True, reset_rl_history=True, "The current line") def _get_cursor_offset(self): return self._cursor_offset - def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=True, clear_special_mode=True): + def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=False, clear_special_mode=True): if update_completion: self.update_completion() if reset_rl_history: From e31a8b5dd9eccc18f8941ea153ceb04505594446 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 18 Oct 2014 00:07:08 -0400 Subject: [PATCH 0089/1650] Added most options to config left out ones I'm thinking might be deprecated or don't apply to bpython-curtsies --- bpython/sample-config | 57 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/bpython/sample-config b/bpython/sample-config index bcb2c0901..e75378649 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -15,29 +15,68 @@ auto_display_list = True # Syntax highlighting as you type (default: True). -syntax = True +# syntax = True # Display the arg spec (list of arguments) for callables, # when possible (default: True). -arg_spec = True +# arg_spec = True # History file (default: ~/.pythonhist): -hist_file = ~/.pythonhist +# hist_file = ~/.pythonhist # Number of lines to store in history (set to 0 to disable) (default: 100): -hist_length = 100 +# hist_length = 100 # Soft tab size (default: 4, see pep-8): -tab_length = 4 +# tab_length = 4 # Color schemes should be put in $XDG_CONFIG_HOME/bpython/ e.g. to use the theme # $XDG_CONFIG_HOME/bpython/foo.theme set color_scheme = foo. Leave blank or set # to "default" to use the default theme -color_scheme = default +# color_scheme = default # External editor to use for editing the current line, block, or full history -editor = vi +# Default is to try $EDITOR and $VISUAL, then vi - but if you uncomment +# the line below that will take precedence +# editor = vi + +# Whether to append .py to the filename while saving session to a file. +# (default: False) +# save_append_py = False + +# The name of a helper executable that should perform pastebin upload on bpython’s behalf. +#pastebin_helper = gist.py [keyboard] -pastebin = F8 -save = C-s + +# pastebin = F8 +# last_output = F9 +# reimport = F6 +# help = F1 +# toggle_file_watch = F5 +# save = C-s +# undo = C-r +# up_one_line = C-p +# down_one_line = C-n +# cut_to_buffer = C-k +# search = C-o +# yank_from_buffer = C-y +# clear_word = C-w +# clear_line = C-u +# clear_screen = C-l +# show_source = F2 +# exit = C-d +# external_editor = F7 +# edit_config = F3 +# +[curtsies] + +# Allow the the completion and docstring box above the current line +# (default: False) +# list_above = False + +# Enables two fish (the shell) style features: +# Previous line key will search for the current line (like reverse incremental +# search) and right arrow will complete the current line with the first match +# from history. (default: True) +# right_arrow_completion = True From 9d8a6aaeb674fb213de596447d438851159f5294 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 18 Oct 2014 00:20:10 -0400 Subject: [PATCH 0090/1650] remove sample-config from setup.py now it's in the bpython package for easier programmatic access --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f7bc9b092..685894e90 100755 --- a/setup.py +++ b/setup.py @@ -187,7 +187,7 @@ def initialize_options(self): packages = packages, data_files = data_files, package_data = { - 'bpython': ['logo.png', 'sample-config'], + 'bpython': ['logo.png'], 'bpython.translations': mo_files, 'bpython.test': ['test.config', 'test.theme'] }, From 20f0b3b0add029daeece8061955c38f53765bcb0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 18 Oct 2014 01:39:58 -0400 Subject: [PATCH 0091/1650] remove outdated testing beeper module --- bpython/curtsiesfrontend/beeper.py | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 bpython/curtsiesfrontend/beeper.py diff --git a/bpython/curtsiesfrontend/beeper.py b/bpython/curtsiesfrontend/beeper.py deleted file mode 100644 index 287013c09..000000000 --- a/bpython/curtsiesfrontend/beeper.py +++ /dev/null @@ -1,9 +0,0 @@ -import time -import sys - -if __name__ == '__main__': - while True: - sys.stdout.write('beep\n') - sys.stdout.flush() - time.sleep(5) - From 9fa5e7e4e23e90e6d6cebf7a74c0ca67e955f8d7 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 18 Oct 2014 01:49:26 -0400 Subject: [PATCH 0092/1650] fix hacky __import__ override fixes #418 --- bpython/curtsiesfrontend/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 785f3d149..45ba07e7d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -339,11 +339,11 @@ def __enter__(self): self.orig_import = __builtins__['__import__'] if self.watcher: - old_module_locations = {} # for readding modules if they fail to load + old_module_locations = {} # for reading modules if they fail to load @functools.wraps(self.orig_import) def new_import(name, globals={}, locals={}, fromlist=[], level=-1): try: - m = self.orig_import(name, globals=globals, locals=locals, fromlist=fromlist) + m = self.orig_import(name, globals=globals, locals=locals, fromlist=fromlist, level=level) except: if name in old_module_locations: loc = old_module_locations[name] From 4a43c116630a56a0df875b819a735928141e8028 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 18 Oct 2014 02:17:40 -0400 Subject: [PATCH 0093/1650] first pass at suspending Doesn't put the cursor back very well at all, but since we're going to have no idea what has happened while Python was suspended there's probably not much we can do. --- bpython/curtsies.py | 12 +++++++++++- bpython/curtsiesfrontend/repl.py | 16 +++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 4e6bf3e17..431fe4f09 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -99,6 +99,14 @@ def event_or_refresh(timeout=None): if starttime + timeout < time.time() or e is not None: yield e + def on_suspend(): + window.__exit__(None, None, None) + input_generator.__exit__(None, None, None) + + def after_suspend(): + input_generator.__enter__() + window.__enter__() + global repl # global for easy introspection `from bpython.curtsies import repl` with Repl(config=config, locals_=locals_, @@ -109,7 +117,9 @@ def event_or_refresh(timeout=None): banner=banner, interp=interp, interactive=interactive, - orig_tcattrs=input_generator.original_stty) as repl: + orig_tcattrs=input_generator.original_stty, + on_suspend=on_suspend, + after_suspend=after_suspend) as repl: repl.height, repl.width = window.t.height, window.t.width def process_event(e): diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 45ba07e7d..2a2e5c871 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -219,7 +219,9 @@ def __init__(self, banner=None, interp=None, interactive=True, - orig_tcattrs=None): + orig_tcattrs=None, + on_suspend=lambda *args: None, + after_suspend=lambda *args: None): """ locals_ is a mapping of locals to pass into the interpreter config is a bpython config.Struct with config attributes @@ -298,6 +300,8 @@ def smarter_request_reload(desc): # because there wasn't room to display everything self._cursor_offset = 0 # from the left, 0 means first char self.orig_tcattrs = orig_tcattrs # useful for shelling out with normal terminal + self.on_suspend = on_suspend + self.after_suspend = after_suspend self.coderunner = CodeRunner(self.interp, self.request_refresh) self.stdout = FakeOutput(self.coderunner, self.send_to_stdout) @@ -335,7 +339,9 @@ def __enter__(self): sys.stderr = self.stderr sys.stdin = self.stdin self.orig_sigwinch_handler = signal.getsignal(signal.SIGWINCH) + self.orig_sigtstp_handler = signal.getsignal(signal.SIGTSTP) signal.signal(signal.SIGWINCH, self.sigwinch_handler) + signal.signal(signal.SIGTSTP, self.sigtstp_handler) self.orig_import = __builtins__['__import__'] if self.watcher: @@ -369,6 +375,7 @@ def __exit__(self, *args): sys.stdout = self.orig_stdout sys.stderr = self.orig_stderr signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) + signal.signal(signal.SIGTSTP, self.orig_sigtstp_handler) __builtins__['__import__'] = self.orig_import def sigwinch_handler(self, signum, frame): @@ -379,6 +386,13 @@ def sigwinch_handler(self, signum, frame): logger.info('sigwinch! Changed from %r to %r', (old_rows, old_columns), (self.height, self.width)) logger.info('decreasing scroll offset by %d to %d', cursor_dy, self.scroll_offset) + def sigtstp_handler(self, signum, frame): + self.__exit__() + self.on_suspend() + os.kill(os.getpid(), signal.SIGTSTP) + self.after_suspend() + self.__enter__() + def startup(self): """ Execute PYTHONSTARTUP file if it exits. Call this after front From 06b9400565bfb34c6c191b2c930048af5ccde512 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 18 Oct 2014 02:53:53 -0400 Subject: [PATCH 0094/1650] formatting and tests --- bpython/curtsiesfrontend/repl.py | 14 ++++++-------- bpython/test/test_curtsies_painting.py | 5 +++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2a2e5c871..ace8a66fc 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -547,17 +547,15 @@ def process_key_event(self, e): def get_last_word(self): def last_word(line): - if not line: - return '' - return line.split().pop() + return line.split().pop() if line else '' previous_word = last_word(self.rl_history.entry) word = last_word(self.rl_history.back()) - line=self.current_line - self._set_current_line(line[:len(line)-len(previous_word)]+word, reset_rl_history=False) - - self._set_cursor_offset(self.cursor_offset-len(previous_word)+len(word), reset_rl_history=False) - + line = self.current_line + self._set_current_line(line[:len(line)-len(previous_word)] + word, + reset_rl_history=False) + self._set_cursor_offset(self.cursor_offset-len(previous_word) + len(word), + reset_rl_history=False) def incremental_search(self, reverse=False, include_current=False): if self.special_mode == None: diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index ce034a1db..098a9d31f 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -40,6 +40,11 @@ def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None): if cursor_row_col is not None: self.assertEqual(cursor_pos, cursor_row_col) +class TestCurtsiesPaintingTest(TestCurtsiesPainting): + + def test_history_is_cleared(self): + self.assertEqual(self.repl.rl_history.entries, ['']) + class TestCurtsiesPaintingSimple(TestCurtsiesPainting): def test_startup(self): From e854572cffe5f67e4f53d294c9155ffa75882562 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 18 Oct 2014 12:02:01 -0400 Subject: [PATCH 0095/1650] Automatic refresh only every 1 second Going to phase out the scheduled refreshes, and this will make bugs more obvious in the meantime --- bpython/curtsies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 431fe4f09..f2ad92145 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -82,9 +82,9 @@ def request_refresh(when='now'): def event_or_refresh(timeout=None): if timeout is None: - timeout = .2 + timeout = 1 else: - timeout = min(.2, timeout) + timeout = min(1, timeout) starttime = time.time() while True: t = time.time() From 4d058700b8a4033395d2211a9129d2427162fa1c Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 18 Oct 2014 12:02:53 -0400 Subject: [PATCH 0096/1650] rename special_mode -> incremental_search_mode --- bpython/curtsiesfrontend/repl.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index ace8a66fc..cb15643cd 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -319,7 +319,7 @@ def smarter_request_reload(desc): self.current_match = None # currently tab-selected autocompletion suggestion self.list_win_visible = False # whether the infobox (suggestions, docstring) is visible self.watching_files = False # auto reloading turned on - self.special_mode = None # 'reverse_incremental_search' and 'incremental_search' + self.incremental_search_mode = None # 'reverse_incremental_search' and 'incremental_search' self.incremental_search_target = '' self.original_modules = sys.modules.keys() @@ -493,7 +493,7 @@ def process_key_event(self, e): self.incremental_search(reverse=True) elif e in ("",): self.incremental_search() - elif e in ("", '') and self.special_mode: + elif e in ("", '') and self.incremental_search_mode: self.add_to_incremental_search(self, backspace=True) elif e in self.edit_keys.cut_buffer_edits: self.readline_kill(e) @@ -538,7 +538,7 @@ def process_key_event(self, e): elif e in key_dispatch[self.config.edit_current_block_key]: self.send_current_block_to_external_editor() elif e in [""]: #ESC - self.special_mode = None + self.incremental_search_mode = None elif e in [""]: self.add_normal_character(' ') else: @@ -558,7 +558,7 @@ def last_word(line): reset_rl_history=False) def incremental_search(self, reverse=False, include_current=False): - if self.special_mode == None: + if self.incremental_search_mode == None: self.rl_history.enter(self.current_line) self.incremental_search_target = '' else: @@ -575,9 +575,9 @@ def incremental_search(self, reverse=False, include_current=False): self._set_cursor_offset(len(self.current_line), reset_rl_history=False, clear_special_mode=False) if reverse: - self.special_mode = 'reverse_incremental_search' + self.incremental_search_mode = 'reverse_incremental_search' else: - self.special_mode = 'incremental_search' + self.incremental_search_mode = 'incremental_search' def readline_kill(self, e): func = self.edit_keys[e] @@ -733,7 +733,7 @@ def toggle_file_watch(self): def add_normal_character(self, char): if len(char) > 1 or is_nop(char): return - if self.special_mode: + if self.incremental_search_mode: self.add_to_incremental_search(char) else: self.current_line = (self.current_line[:self.cursor_offset] + @@ -755,9 +755,9 @@ def add_to_incremental_search(self, char=None, backspace=False): self.incremental_search_target = self.incremental_search_target[:-1] else: self.incremental_search_target += char - if self.special_mode == 'reverse_incremental_search': + if self.incremental_search_mode == 'reverse_incremental_search': self.incremental_search(reverse=True, include_current=True) - elif self.special_mode == 'incremental_search': + elif self.incremental_search_mode == 'incremental_search': self.incremental_search(include_current=True) else: raise ValueError('add_to_incremental_search should only be called in a special mode') @@ -849,7 +849,7 @@ def run_code_and_maybe_finish(self, for_code=None): if err: indent = 0 - + #TODO This should be printed ABOVE the error that just happened instead # or maybe just thrown away and not shown if self.current_stdouterr_line: @@ -935,7 +935,7 @@ def current_line_formatted(self): """The colored current line (no prompt, not wrapped)""" if self.config.syntax: fs = bpythonparse(format(self.tokenize(self.current_line), self.formatter)) - if self.special_mode: + if self.incremental_search_mode: if self.incremental_search_target in self.current_line: fs = fmtfuncs.on_magenta(self.incremental_search_target).join(fs.split(self.incremental_search_target)) elif self.rl_history.saved_line and self.rl_history.saved_line in self.current_line: @@ -969,10 +969,10 @@ def display_buffer_lines(self): @property def display_line_with_prompt(self): """colored line with prompt""" - if self.special_mode == 'reverse_incremental_search': + if self.incremental_search_mode == 'reverse_incremental_search': return func_for_letter(self.config.color_scheme['prompt'])( '(reverse-i-search)`%s\': ' % (self.incremental_search_target,)) + self.current_line_formatted - elif self.special_mode == 'incremental_search': + elif self.incremental_search_mode == 'incremental_search': return func_for_letter(self.config.color_scheme['prompt'])( '(i-search)`%s\': ' % (self.incremental_search_target,)) + self.current_line_formatted return (func_for_letter(self.config.color_scheme['prompt'])(self.ps1) @@ -1019,7 +1019,6 @@ def paint(self, about_to_exit=False, user_quit=False): less state is cool! """ # The hairiest function in the curtsies - a cleanup would be great. - if about_to_exit: self.clean_up_current_line_for_exit() # exception to not changing state! @@ -1234,7 +1233,7 @@ def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=Fa if reset_rl_history: self.rl_history.reset() if clear_special_mode: - self.special_mode = None + self.incremental_search_mode = None self._cursor_offset = offset self.update_completion() cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, From f8b56d16997ed2fd5407ef45fb55e0808bbedc2e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 18 Oct 2014 02:52:43 -0400 Subject: [PATCH 0097/1650] add failing test --- bpython/test/test_curtsies_painting.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 098a9d31f..26c29fedf 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -450,3 +450,16 @@ def test_cursor_stays_at_bottom_of_screen(self): u"'random'", u">>> "] self.assert_paint_ignoring_formatting(screen, (2, 4)) + + def test_unhighlight_paren_bug(self): + """infobox showing up during intermediate render was causing this to fail, #371""" + self.enter('(') + screen = [u">>> (", + u"... "] + self.assert_paint_ignoring_formatting(screen) + + with output_to_repl(self.repl): + self.repl.process_event(')') + screen = fsarray([cyan(u">>> ")+yellow('('), + green(u"... ")+yellow(')')]) + self.assert_paint(screen, (1, 3)) From 81c2197e1beecfd8ddb86586ae48c49ef4ee0949 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 18 Oct 2014 11:58:29 -0400 Subject: [PATCH 0098/1650] fix #370 --- bpython/curtsiesfrontend/repl.py | 14 +++++++------ bpython/test/test_curtsies_painting.py | 28 +++++++++++++++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index cb15643cd..f11c9a505 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -594,8 +594,6 @@ def on_enter(self, insert_into_history=True): self.unhighlight_paren() # in unhighlight_paren self.highlighted_paren = None - self.rl_history.append(self.current_line) - self.rl_history.last() self.history.append(self.current_line) self.push(self.current_line, insert_into_history=insert_into_history) @@ -1038,6 +1036,10 @@ def paint(self, about_to_exit=False, user_quit=False): arr = FSArray(0, width) #TODO test case of current line filling up the whole screen (there aren't enough rows to show it) + current_line = paint.paint_current_line(min_height, width, self.current_cursor_line) + # needs to happen before we calculate contents of history because calculating + # self.current_cursor_line has the side effect of unhighlighting parens in buffer + def move_screen_up(current_line_start_row): # move screen back up a screen minus a line while current_line_start_row < 0: @@ -1084,12 +1086,8 @@ def move_screen_up(current_line_start_row): history = paint.paint_history(current_line_start_row, width, self.lines_for_display) arr[:history.height,:history.width] = history - - - self.inconsistent_history = False - current_line = paint.paint_current_line(min_height, width, self.current_cursor_line) if user_quit: # quit() or exit() in interp current_line_start_row = current_line_start_row - current_line.height logger.debug("---current line row slice %r, %r", current_line_start_row, current_line_start_row + current_line.height) @@ -1223,11 +1221,14 @@ def _set_current_line(self, line, update_completion=True, reset_rl_history=True, self.rl_history.reset() if clear_special_mode: self.special_mode = None + self.unhighlight_paren() current_line = property(_get_current_line, _set_current_line, None, "The current line") def _get_cursor_offset(self): return self._cursor_offset def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=False, clear_special_mode=True): + if self._cursor_offset == offset: + return if update_completion: self.update_completion() if reset_rl_history: @@ -1236,6 +1237,7 @@ def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=Fa self.incremental_search_mode = None self._cursor_offset = offset self.update_completion() + self.unhighlight_paren() cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, "The current cursor offset from the front of the line") def echo(self, msg, redraw=True): diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 26c29fedf..15018dff1 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -62,7 +62,7 @@ def test_run_line(self): orig_stdout = sys.stdout sys.stdout = self.repl.stdout [self.repl.add_normal_character(c) for c in '1 + 1'] - self.repl.on_enter() + self.repl.on_enter(insert_into_history=False) screen = fsarray([u'>>> 1 + 1', '2', 'Welcome to']) self.assert_paint_ignoring_formatting(screen, (1, 1)) finally: @@ -105,7 +105,8 @@ def enter(self, line=None): if line is not None: self.repl.current_line = line with output_to_repl(self.repl): - self.repl.on_enter() + self.repl.on_enter(insert_into_history=False) + self.assertEqual(self.repl.rl_history.entries, ['']) self.send_refreshes() def undo(self): @@ -432,7 +433,7 @@ def test_cursor_stays_at_bottom_of_screen(self): self.repl.width = 50 self.repl.current_line = "__import__('random').__name__" with output_to_repl(self.repl): - self.repl.on_enter() + self.repl.on_enter(insert_into_history=False) screen = [u">>> __import__('random').__name__", u"'random'"] self.assert_paint_ignoring_formatting(screen) @@ -451,15 +452,28 @@ def test_cursor_stays_at_bottom_of_screen(self): u">>> "] self.assert_paint_ignoring_formatting(screen, (2, 4)) - def test_unhighlight_paren_bug(self): - """infobox showing up during intermediate render was causing this to fail, #371""" + def test_unhighlight_paren_bugs(self): + """two previous bugs, paren did't highlight until next render + and paren didn't unhighlight until enter""" + self.assertEqual(self.repl.rl_history.entries, ['']) self.enter('(') + self.assertEqual(self.repl.rl_history.entries, ['']) screen = [u">>> (", u"... "] + self.assertEqual(self.repl.rl_history.entries, ['']) self.assert_paint_ignoring_formatting(screen) + self.assertEqual(self.repl.rl_history.entries, ['']) with output_to_repl(self.repl): + self.assertEqual(self.repl.rl_history.entries, ['']) self.repl.process_event(')') + self.assertEqual(self.repl.rl_history.entries, ['']) + screen = fsarray([cyan(u">>> ")+on_magenta(bold(red('('))), + green(u"... ")+on_magenta(bold(red(')')))]) + self.assert_paint(screen, (1, 5)) + + with output_to_repl(self.repl): + self.repl.process_event(' ') screen = fsarray([cyan(u">>> ")+yellow('('), - green(u"... ")+yellow(')')]) - self.assert_paint(screen, (1, 3)) + green(u"... ")+yellow(')')+bold(cyan(" "))]) + self.assert_paint(screen, (1, 6)) From 329f3706c5810aff51490cc3b4f7b8f55df7fc71 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 18 Oct 2014 14:40:02 -0400 Subject: [PATCH 0099/1650] allow more key configuration, prevent doublebinds --- bpython/config.py | 79 ++++++++++++++------- bpython/curtsiesfrontend/manual_readline.py | 17 ++--- bpython/curtsiesfrontend/repl.py | 3 +- bpython/sample-config | 3 + bpython/test/test_config.py | 13 +++- 5 files changed, 78 insertions(+), 37 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index f046d0ca5..a6f0e3569 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -35,7 +35,7 @@ def loadini(struct, configfile): config_path = os.path.expanduser(configfile) config = ConfigParser() - fill_config_with_default_values(config, { + defaults = { 'general': { 'arg_spec': True, 'auto_display_list': True, @@ -61,8 +61,15 @@ def loadini(struct, configfile): 'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')) }, 'keyboard': { + 'backspace': 'C-h', + 'left': 'C-b', + 'right': 'C-f', + 'beginning_of_line': 'C-a', + 'end_of_line': 'C-e', + 'transpose_chars': 'C-t', 'clear_line': 'C-u', 'clear_screen': 'C-l', + 'kill_line': 'C-k', 'clear_word': 'C-w', 'cut_to_buffer': 'C-k', 'delete': 'C-d', @@ -90,7 +97,8 @@ def loadini(struct, configfile): 'curtsies': { 'list_above' : False, 'right_arrow_completion' : True, - }}) + }} + fill_config_with_default_values(config, defaults) if not config.read(config_path): # No config file. If the user has it in the old place then complain if os.path.isfile(os.path.expanduser('~/.bpython.ini')): @@ -99,6 +107,18 @@ def loadini(struct, configfile): "%s\n" % default_config_path()) sys.exit(1) + def get_key_no_doublebind(attr, already_used={}): + """Clears any other configured keybindings using this key""" + key = config.get('keyboard', attr) + if key in already_used: + default = defaults['keyboard'][already_used[key]] + if default in already_used: + setattr(struct, '%s_key' % already_used[key], '') + else: + setattr(struct, '%s_key' % already_used[key], default) + already_used[key] = attr + return key + struct.config_path = config_path struct.dedent_after = config.getint('general', 'dedent_after') @@ -115,28 +135,39 @@ def loadini(struct, configfile): struct.hist_length = config.getint('general', 'hist_length') struct.hist_duplicates = config.getboolean('general', 'hist_duplicates') struct.flush_output = config.getboolean('general', 'flush_output') - struct.pastebin_key = config.get('keyboard', 'pastebin') - struct.save_key = config.get('keyboard', 'save') - struct.search_key = config.get('keyboard', 'search') - struct.show_source_key = config.get('keyboard', 'show_source') - struct.suspend_key = config.get('keyboard', 'suspend') - struct.toggle_file_watch_key = config.get('keyboard', 'toggle_file_watch') - struct.undo_key = config.get('keyboard', 'undo') - struct.reimport_key = config.get('keyboard', 'reimport') - struct.up_one_line_key = config.get('keyboard', 'up_one_line') - struct.down_one_line_key = config.get('keyboard', 'down_one_line') - struct.cut_to_buffer_key = config.get('keyboard', 'cut_to_buffer') - struct.yank_from_buffer_key = config.get('keyboard', 'yank_from_buffer') - struct.clear_word_key = config.get('keyboard', 'clear_word') - struct.clear_line_key = config.get('keyboard', 'clear_line') - struct.clear_screen_key = config.get('keyboard', 'clear_screen') - struct.delete_key = config.get('keyboard', 'delete') - struct.exit_key = config.get('keyboard', 'exit') - struct.last_output_key = config.get('keyboard', 'last_output') - struct.edit_config_key = config.get('keyboard', 'edit_config') - struct.edit_current_block_key = config.get('keyboard', 'edit_current_block') - struct.external_editor_key = config.get('keyboard', 'external_editor') - struct.help_key = config.get('keyboard', 'help') + + struct.pastebin_key = get_key_no_doublebind('pastebin') + struct.save_key = get_key_no_doublebind('save') + struct.search_key = get_key_no_doublebind('search') + struct.show_source_key = get_key_no_doublebind('show_source') + struct.suspend_key = get_key_no_doublebind('suspend') + struct.toggle_file_watch_key = get_key_no_doublebind('toggle_file_watch') + struct.undo_key = get_key_no_doublebind('undo') + struct.reimport_key = get_key_no_doublebind('reimport') + struct.up_one_line_key = get_key_no_doublebind('up_one_line') + struct.down_one_line_key = get_key_no_doublebind('down_one_line') + struct.cut_to_buffer_key = get_key_no_doublebind('cut_to_buffer') + struct.yank_from_buffer_key = get_key_no_doublebind('yank_from_buffer') + struct.clear_word_key = get_key_no_doublebind('clear_word') + struct.backspace_key = get_key_no_doublebind('backspace') + struct.clear_line_key = get_key_no_doublebind('clear_line') + struct.clear_screen_key = get_key_no_doublebind('clear_screen') + struct.delete_key = get_key_no_doublebind('delete') + + struct.left_key = get_key_no_doublebind('left') + struct.right_key = get_key_no_doublebind('right') + struct.end_of_line_key = get_key_no_doublebind('end_of_line') + struct.beginning_of_line_key = get_key_no_doublebind('beginning_of_line') + struct.transpose_chars_key = get_key_no_doublebind('transpose_chars') + struct.clear_line_key = get_key_no_doublebind('clear_line') + struct.clear_screen_key = get_key_no_doublebind('clear_screen') + struct.kill_line_key = get_key_no_doublebind('kill_line') + struct.exit_key = get_key_no_doublebind('exit') + struct.last_output_key = get_key_no_doublebind('last_output') + struct.edit_config_key = get_key_no_doublebind('edit_config') + struct.edit_current_block_key = get_key_no_doublebind('edit_current_block') + struct.external_editor_key = get_key_no_doublebind('external_editor') + struct.help_key = get_key_no_doublebind('help') struct.pastebin_confirm = config.getboolean('general', 'pastebin_confirm') struct.pastebin_url = config.get('general', 'pastebin_url') diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 4be8bb961..3c608b14d 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -141,22 +141,22 @@ def kills_ahead(func): func.kills = 'ahead' return func -@edit_keys.on('') +@edit_keys.on(config='left_key') @edit_keys.on('') def left_arrow(cursor_offset, line): return max(0, cursor_offset - 1), line -@edit_keys.on('') +@edit_keys.on(config='right_key') @edit_keys.on('') def right_arrow(cursor_offset, line): return min(len(line), cursor_offset + 1), line -@edit_keys.on('') +@edit_keys.on(config='beginning_of_line_key') @edit_keys.on('') def beginning_of_line(cursor_offset, line): return 0, line -@edit_keys.on('') +@edit_keys.on(config='end_of_line_key') @edit_keys.on('') def end_of_line(cursor_offset, line): return len(line), line @@ -188,9 +188,8 @@ def delete(cursor_offset, line): return (cursor_offset, line[:cursor_offset] + line[cursor_offset+1:]) -@edit_keys.on('') @edit_keys.on('') -@edit_keys.on(config='delete_key') +@edit_keys.on(config='backspace_key') def backspace(cursor_offset, line): if cursor_offset == 0: return cursor_offset, line @@ -201,7 +200,6 @@ def backspace(cursor_offset, line): return (cursor_offset - 1, line[:cursor_offset - 1] + line[cursor_offset:]) -@edit_keys.on('') @edit_keys.on(config='clear_line_key') def delete_from_cursor_back(cursor_offset, line): return 0, line[cursor_offset:] @@ -215,7 +213,6 @@ def delete_rest_of_word(cursor_offset, line): return (cursor_offset, line[:cursor_offset] + line[m.start()+cursor_offset+1:], line[cursor_offset:m.start()+cursor_offset+1]) -@edit_keys.on('') @edit_keys.on(config='clear_word_key') @kills_behind def delete_word_to_cursor(cursor_offset, line): @@ -231,7 +228,7 @@ def yank_prev_prev_killed_text(cursor_offset, line, cut_buffer): #TODO not imple def yank_prev_killed_text(cursor_offset, line, cut_buffer): return cursor_offset+len(cut_buffer), line[:cursor_offset] + cut_buffer + line[cursor_offset:] -@edit_keys.on('') +@edit_keys.on(config='transpose_chars_key') def transpose_character_before_cursor(cursor_offset, line): return (min(len(line), cursor_offset + 1), line[:cursor_offset-1] + @@ -253,7 +250,7 @@ def delete_line(cursor_offset, line): def uppercase_next_word(cursor_offset, line): return cursor_offset, line #TODO Not implemented -@edit_keys.on('') +@edit_keys.on(config='kill_line_key') @kills_ahead def delete_from_cursor_forward(cursor_offset, line): return cursor_offset, line[:cursor_offset], line[cursor_offset:] diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f11c9a505..c682bb052 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -493,7 +493,8 @@ def process_key_event(self, e): self.incremental_search(reverse=True) elif e in ("",): self.incremental_search() - elif e in ("", '') and self.incremental_search_mode: + elif (e in ("",) + key_dispatch[self.config.backspace_key] + and self.incremental_search_mode): self.add_to_incremental_search(self, backspace=True) elif e in self.edit_keys.cut_buffer_edits: self.readline_kill(e) diff --git a/bpython/sample-config b/bpython/sample-config index e75378649..88084e891 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -49,6 +49,8 @@ auto_display_list = True [keyboard] +# All key bindings are shown commented out with their default binding + # pastebin = F8 # last_output = F9 # reimport = F6 @@ -61,6 +63,7 @@ auto_display_list = True # cut_to_buffer = C-k # search = C-o # yank_from_buffer = C-y +# backspace = C-h # clear_word = C-w # clear_line = C-u # clear_screen = C-l diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index d36ffe814..9bacc1f06 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -1,5 +1,6 @@ import os import unittest +import tempfile from bpython import config @@ -18,5 +19,13 @@ def test_load_theme(self): config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, defaults) self.assertEquals(struct.color_scheme, expected) -if __name__ == '__main__': - unittest.main() + def test_load_config(self): + struct = config.Struct() + with tempfile.NamedTemporaryFile() as f: + f.write(''.encode('utf8')) + f.write('[keyboard]\nhelp = C-h\n'.encode('utf8')) + f.flush() + config.loadini(struct, f.name) + self.assertEqual(struct.help_key, 'C-h') + self.assertEqual(struct.backspace_key, '') + From cb0a166a3102fc0a45890e0c401f13b0108c7e9f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 20 Oct 2014 16:37:31 +0200 Subject: [PATCH 0100/1650] Document pastebin_helper a bit better Signed-off-by: Sebastian Ramacher --- bpython/sample-config | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bpython/sample-config b/bpython/sample-config index 88084e891..46c3460d9 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -12,7 +12,7 @@ # Display the autocomplete list as you type (default: True). # When this is off, you can hit tab to see the suggestions. -auto_display_list = True +# auto_display_list = True # Syntax highlighting as you type (default: True). # syntax = True @@ -44,7 +44,8 @@ auto_display_list = True # (default: False) # save_append_py = False -# The name of a helper executable that should perform pastebin upload on bpython’s behalf. +# The name of a helper executable that should perform pastebin upload on +# bpython’s behalf. If unset, bpython uploads pastes to bpaste.net. (default: ) #pastebin_helper = gist.py [keyboard] @@ -70,8 +71,8 @@ auto_display_list = True # show_source = F2 # exit = C-d # external_editor = F7 -# edit_config = F3 -# +# edit_config = F3 + [curtsies] # Allow the the completion and docstring box above the current line From 05a2d40ddc9b2063c6053bed5df677bc90f7a092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Mon, 20 Oct 2014 21:28:51 +0200 Subject: [PATCH 0101/1650] Raise an exception if safe_eval fails instead of returning a sentinel. --- bpython/autocomplete.py | 31 +++++++++++++++++-------------- bpython/test/test_autocomplete.py | 5 ++--- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 73868c51f..914eef150 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -205,8 +205,9 @@ def matches(cls, cursor_offset, line, locals_, **kwargs): return None start, end, orig = r _, _, dexpr = lineparts.current_dict(cursor_offset, line) - obj = safe_eval(dexpr, locals_) - if obj is SafeEvalFailed: + try: + obj = safe_eval(dexpr, locals_) + except EvaluationError: return [] if obj and isinstance(obj, type({})) and obj.keys(): return ["{!r}]".format(k) for k in obj.keys() if repr(k).startswith(orig)] @@ -288,23 +289,20 @@ def matches(cls, cursor_offset, line, **kwargs): return [match for match in matches if not match.startswith('_')] return matches -class SafeEvalFailed(Exception): - """If this object is returned, safe_eval failed""" - # Because every normal Python value is a possible return value of safe_eval + +class EvaluationError(Exception): + """Raised if an exception occurred in safe_eval.""" + def safe_eval(expr, namespace): """Not all that safe, just catches some errors""" - if expr.isdigit(): - # Special case: float literal, using attrs here will result in - # a SyntaxError - return SafeEvalFailed try: - obj = eval(expr, namespace) - return obj + return eval(expr, namespace) except (NameError, AttributeError, SyntaxError): # If debugging safe_eval, raise this! # raise - return SafeEvalFailed + raise EvaluationError + def attr_matches(text, namespace, autocomplete_mode): """Taken from rlcompleter.py and bent to my will. @@ -318,8 +316,13 @@ def attr_matches(text, namespace, autocomplete_mode): return [] expr, attr = m.group(1, 3) - obj = safe_eval(expr, namespace) - if obj is SafeEvalFailed: + if expr.isdigit(): + # Special case: float literal, using attrs here will result in + # a SyntaxError + return [] + try: + obj = safe_eval(expr, namespace) + except EvaluationError: return [] with inspection.AttrCleaner(obj): matches = attr_lookup(obj, expr, attr, autocomplete_mode) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index fc47a251a..3a4ee4cc7 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -14,10 +14,9 @@ def skip(f): class TestSafeEval(unittest.TestCase): def test_catches_syntax_error(self): - try: + with self.assertRaises(autocomplete.EvaluationError): autocomplete.safe_eval('1re',{}) - except: - self.fail('safe_eval raises an error') + class TestFormatters(unittest.TestCase): From acdbb1a9ca73b43b2a56b9372ded6859f5945721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Mon, 20 Oct 2014 21:36:19 +0200 Subject: [PATCH 0102/1650] Make test work under Python 2.6. --- bpython/test/test_autocomplete.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 3a4ee4cc7..1f7d91b78 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -14,8 +14,8 @@ def skip(f): class TestSafeEval(unittest.TestCase): def test_catches_syntax_error(self): - with self.assertRaises(autocomplete.EvaluationError): - autocomplete.safe_eval('1re',{}) + self.assertRaises(autocomplete.EvaluationError, + autocomplete.safe_eval, '1re', {}) class TestFormatters(unittest.TestCase): From 3ce0ad3d9879f22559de8b77beeca68563bc0db3 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 23 Oct 2014 12:37:44 -0400 Subject: [PATCH 0103/1650] fix timed refresh behavior, but ugly --- bpython/curtsies.py | 47 ++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index f2ad92145..40a4d27fb 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -82,22 +82,39 @@ def request_refresh(when='now'): def event_or_refresh(timeout=None): if timeout is None: - timeout = 1 - else: - timeout = min(1, timeout) - starttime = time.time() + timeout = 2**25 # a year while True: - t = time.time() - refresh_requests.sort(key=lambda r: 0 if r.when == 'now' else r.when) - if refresh_requests and (refresh_requests[0].when == 'now' or refresh_requests[-1].when < t): - yield refresh_requests.pop(0) - elif reload_requests: - e = reload_requests.pop() - yield e - else: - e = input_generator.send(timeout) - if starttime + timeout < time.time() or e is not None: + starttime = time.time() + while True: + t = time.time() + refresh_requests.sort(key=lambda r: 0 if r.when == 'now' else r.when) + if refresh_requests and (refresh_requests[0].when == 'now' or refresh_requests[-1].when < t): + yield refresh_requests.pop(0) + elif reload_requests: + e = reload_requests.pop() yield e + else: + if refresh_requests: + next_refresh = refresh_requests.pop(0) + time_until_next_scheduled_event = max(0, next_refresh.when - t) + else: + next_refresh = None + time_until_next_scheduled_event = 2**25 + + time_to_wait = min(time_until_next_scheduled_event, max(0, starttime + timeout - t)) + + e = input_generator.send(time_to_wait) + + if next_refresh is not None: + if e is None and time.time() > t + time_until_next_scheduled_event: + yield next_refresh + continue + else: + refresh_requests.insert(0, next_refresh) + + if starttime + timeout < time.time() or e is not None: + yield e + break def on_suspend(): window.__exit__(None, None, None) @@ -144,7 +161,7 @@ def process_event(e): for _, e in izip(find_iterator, event_or_refresh(0)): if e is not None: process_event(e) - for e in event_or_refresh(): + for e in event_or_refresh(None): process_event(e) if __name__ == '__main__': From 700843917e8dc09abd2416fa8d66bbec9bd530c0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 26 Oct 2014 00:37:45 -0400 Subject: [PATCH 0104/1650] Refactor reactor into curtsies Input object --- bpython/curtsies.py | 58 ++++++------------------- bpython/curtsiesfrontend/coderunner.py | 13 +++--- bpython/curtsiesfrontend/events.py | 36 +++++++++++++++ bpython/curtsiesfrontend/filewatch.py | 2 +- bpython/curtsiesfrontend/interaction.py | 12 +++-- bpython/curtsiesfrontend/repl.py | 44 ++++++++++++------- bpython/test/test_curtsies_painting.py | 6 +-- 7 files changed, 97 insertions(+), 74 deletions(-) create mode 100644 bpython/curtsiesfrontend/events.py diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 40a4d27fb..4ba670b97 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -17,6 +17,10 @@ from bpython import args as bpargs from bpython.translations import _ from bpython.importcompletion import find_iterator +from bpython.curtsiesfrontend import events as bpythonevents + +logger = logging.getLogger(__name__) + repl = None # global for `from bpython.curtsies import repl` #WARNING Will be a problem if more than one repl is ever instantiated this way @@ -73,48 +77,9 @@ def mainloop(config, locals_, banner, interp=None, paste=None, interactive=True) hide_cursor=False, extra_bytes_callback=input_generator.unget_bytes) as window: - reload_requests = [] - def request_reload(desc): - reload_requests.append(curtsies.events.ReloadEvent([desc])) - refresh_requests = [] - def request_refresh(when='now'): - refresh_requests.append(curtsies.events.RefreshRequestEvent(when=when)) - - def event_or_refresh(timeout=None): - if timeout is None: - timeout = 2**25 # a year - while True: - starttime = time.time() - while True: - t = time.time() - refresh_requests.sort(key=lambda r: 0 if r.when == 'now' else r.when) - if refresh_requests and (refresh_requests[0].when == 'now' or refresh_requests[-1].when < t): - yield refresh_requests.pop(0) - elif reload_requests: - e = reload_requests.pop() - yield e - else: - if refresh_requests: - next_refresh = refresh_requests.pop(0) - time_until_next_scheduled_event = max(0, next_refresh.when - t) - else: - next_refresh = None - time_until_next_scheduled_event = 2**25 - - time_to_wait = min(time_until_next_scheduled_event, max(0, starttime + timeout - t)) - - e = input_generator.send(time_to_wait) - - if next_refresh is not None: - if e is None and time.time() > t + time_until_next_scheduled_event: - yield next_refresh - continue - else: - refresh_requests.insert(0, next_refresh) - - if starttime + timeout < time.time() or e is not None: - yield e - break + request_refresh = input_generator.event_trigger(bpythonevents.RefreshRequestEvent) + schedule_refresh = input_generator.scheduled_event_trigger(bpythonevents.ScheduledRefreshRequestEvent) + request_reload = input_generator.threadsafe_event_trigger(bpythonevents.ReloadEvent) def on_suspend(): window.__exit__(None, None, None) @@ -128,6 +93,7 @@ def after_suspend(): with Repl(config=config, locals_=locals_, request_refresh=request_refresh, + schedule_refresh=schedule_refresh, request_reload=request_reload, get_term_hw=window.get_term_hw, get_cursor_vertical_diff=window.get_cursor_vertical_diff, @@ -157,11 +123,13 @@ def process_event(e): if paste: process_event(paste) - process_event(None) #priming the pump (do a display before waiting for first event) - for _, e in izip(find_iterator, event_or_refresh(0)): + process_event(None) # do a display before waiting for first event + for _ in find_iterator: + e = input_generator.send(0) if e is not None: process_event(e) - for e in event_or_refresh(None): + + for e in input_generator: process_event(e) if __name__ == '__main__': diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index d2706cecc..e10897ee2 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -68,19 +68,19 @@ class CodeRunner(object): just passes whatever is passed in to run_code(for_code) to the code greenlet """ - def __init__(self, interp=None, stuff_a_refresh_request=lambda:None): + def __init__(self, interp=None, request_refresh=lambda:None): """ interp is an interpreter object to use. By default a new one is created. - stuff_a_refresh_request is a function that will be called each time + request_refresh is a function that will be called each time the running code asks for a refresh - to, for example, update the screen. """ self.interp = interp or code.InteractiveInterpreter() self.source = None self.main_greenlet = greenlet.getcurrent() self.code_greenlet = None - self.stuff_a_refresh_request = stuff_a_refresh_request + self.request_refresh = request_refresh self.code_is_waiting = False # waiting for response from main thread self.sigint_happened_in_main_greenlet = False # sigint happened while in main thread self.orig_sigint_handler = None @@ -125,12 +125,13 @@ def run_code(self, for_code=None): else: request = self.code_greenlet.switch(for_code) + logger.debug('request received from code was %r', request) if not issubclass(request, RequestFromCodeGreenlet): raise ValueError("Not a valid value from code greenlet: %r" % request) if request in [Wait, Refresh]: self.code_is_waiting = True if request == Refresh: - self.stuff_a_refresh_request() + self.request_refresh() return False elif request in [Done, Unfinished]: self._unload_code() @@ -188,7 +189,7 @@ def isatty(self): def test_simple(): orig_stdout = sys.stdout orig_stderr = sys.stderr - c = CodeRunner(stuff_a_refresh_request=lambda: orig_stdout.flush() or orig_stderr.flush()) + c = CodeRunner(request_refresh=lambda: orig_stdout.flush() or orig_stderr.flush()) stdout = FakeOutput(c, orig_stdout.write) sys.stdout = stdout c.load_code('1 + 1') @@ -199,7 +200,7 @@ def test_simple(): def test_exception(): orig_stdout = sys.stdout orig_stderr = sys.stderr - c = CodeRunner(stuff_a_refresh_request=lambda: orig_stdout.flush() or orig_stderr.flush()) + c = CodeRunner(request_refresh=lambda: orig_stdout.flush() or orig_stderr.flush()) def ctrlc(): raise KeyboardInterrupt() stdout = FakeOutput(c, lambda x: ctrlc()) diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py new file mode 100644 index 000000000..1b45c2956 --- /dev/null +++ b/bpython/curtsiesfrontend/events.py @@ -0,0 +1,36 @@ +"""Non-keybaord events used in bpython curtsies REPL""" +import time + +import curtsies.events + + +class ReloadEvent(curtsies.events.Event): + """Request to rerun REPL session ASAP because imported modules changed""" + def __init__(self, files_modified=('?',)): + self.files_modified = files_modified + + def __repr__(self): + return "" % (' & '.join(self.files_modified)) + + +class RefreshRequestEvent(curtsies.events.Event): + """Request to refresh REPL display ASAP""" + def __init__(self, who='?'): + self.who = who + + def __repr__(self): + return "" % (self.who,) + + +class ScheduledRefreshRequestEvent(curtsies.events.ScheduledEvent): + """Request to refresh the REPL display at some point in the future + + Used to schedule the dissapearance of status bar message that only show + for a few seconds""" + def __init__(self, when, who='?'): + self.who = who + self.when = when # time.time() + how long + + def __repr__(self): + return ("" % + (self.who, self.when - time.time())) diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 73930730e..57955cbc5 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -63,7 +63,7 @@ def on_any_event(self, event): dirpath = os.path.dirname(event.src_path) paths = [path + '.py' for path in self.dirs[dirpath]] if event.src_path in paths: - self.on_change(event.src_path) + self.on_change(files_modified=[event.src_path]) if __name__ == '__main__': m = ModuleChangedEventHandler([]) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index c58eae11b..1036471c1 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -19,7 +19,10 @@ class StatusBar(BpythonInteraction): functionality in a evented or callback style, but trying to integrate bpython.Repl code. """ - def __init__(self, permanent_text="", refresh_request=lambda: None): + def __init__(self, + permanent_text="", + request_refresh=lambda: None, + schedule_refresh=lambda when: None): self._current_line = '' self.cursor_offset_in_line = 0 self.in_prompt = False @@ -34,7 +37,8 @@ def __init__(self, permanent_text="", refresh_request=lambda: None): self.permanent_stack.append(permanent_text) self.main_greenlet = greenlet.getcurrent() self.request_greenlet = None - self.refresh_request = refresh_request + self.request_refresh = request_refresh + self.schedule_refresh = schedule_refresh def push_permanent_message(self, msg): self._message = '' @@ -54,7 +58,7 @@ def message(self, msg): """Sets a temporary message""" self.message_start_time = time.time() self._message = msg - self.refresh_request(time.time() + self.message_time) + self.schedule_refresh(time.time() + self.message_time) def _check_for_expired_message(self): if self._message and time.time() > self.message_start_time + self.message_time: @@ -129,7 +133,7 @@ def notify(self, msg, n=3): self.message_time = n self.message(msg) self.waiting_for_refresh = True - self.refresh_request() + self.request_refresh() self.main_greenlet.switch(msg) # below Really ought to be called from greenlets other than main because they block diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c682bb052..ae1604e53 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -43,6 +43,7 @@ from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler from bpython.curtsiesfrontend.interaction import StatusBar from bpython.curtsiesfrontend.manual_readline import edit_keys +from bpython.curtsiesfrontend import events as bpythonevents #TODO other autocomplete modes (also fix in other bpython implementations) @@ -213,8 +214,10 @@ class Repl(BpythonRepl): def __init__(self, locals_=None, config=None, - request_refresh=lambda when='now': None, - request_reload=lambda desc: None, get_term_hw=lambda:(50, 10), + request_refresh=lambda: None, + schedule_refresh=lambda when=0: None, + request_reload=lambda desc: None, + get_term_hw=lambda:(50, 10), get_cursor_vertical_diff=lambda: 0, banner=None, interp=None, @@ -259,22 +262,30 @@ def __init__(self, self.reevaluating = False self.fake_refresh_requested = False - def smarter_request_refresh(when='now'): + def smarter_request_refresh(): if self.reevaluating or self.paste_mode: self.fake_refresh_requested = True else: - request_refresh(when=when) + request_refresh() self.request_refresh = smarter_request_refresh - def smarter_request_reload(desc): + def smarter_schedule_refresh(when='now'): + if self.reevaluating or self.paste_mode: + self.fake_refresh_requested = True + else: + schedule_refresh(when=when) + self.schedule_refresh = smarter_schedule_refresh + def smarter_request_reload(files_modified=()): if self.watching_files: - request_reload(desc) + request_reload(files_modified=files_modified) else: pass self.request_reload = smarter_request_reload self.get_term_hw = get_term_hw self.get_cursor_vertical_diff = get_cursor_vertical_diff - self.status_bar = StatusBar('', refresh_request=self.request_refresh) + self.status_bar = StatusBar('', + request_refresh=self.request_refresh, + schedule_refresh=self.schedule_refresh) self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch) logger.debug("starting parent init") super(Repl, self).__init__(interp, config) @@ -423,18 +434,20 @@ def process_event(self, e): logger.debug("processing event %r", e) if isinstance(e, events.Event): - return self.proccess_control_event(e) + return self.process_control_event(e) else: self.last_events.append(e) self.last_events.pop(0) return self.process_key_event(e) - def proccess_control_event(self, e): + def process_control_event(self, e): - if isinstance(e, events.RefreshRequestEvent): - if e.when != 'now': - pass # This is a scheduled refresh - it's really just a refresh (so nop) - elif self.status_bar.has_focus: + if isinstance(e, bpythonevents.ScheduledRefreshRequestEvent): + pass # This is a scheduled refresh - it's really just a refresh (so nop) + + elif isinstance(e, bpythonevents.RefreshRequestEvent): + logger.info('received ASAP refresh request event') + if self.status_bar.has_focus: self.status_bar.process_event(e) else: assert self.coderunner.code_is_waiting @@ -463,7 +476,7 @@ def proccess_control_event(self, e): self.keyboard_interrupt() return - elif isinstance(e, events.ReloadEvent): + elif isinstance(e, bpythonevents.ReloadEvent): if self.watching_files: self.clear_modules_and_reevaluate() self.status_bar.message('Reloaded at ' + time.strftime('%H:%M:%S') + ' because ' + ' & '.join(e.files_modified) + ' modified') @@ -1296,7 +1309,7 @@ def reevaluate(self, insert_into_history=False): self.on_enter(insert_into_history=insert_into_history) while self.fake_refresh_requested: self.fake_refresh_requested = False - self.process_event(events.RefreshRequestEvent()) + self.process_event(bpythonevents.RefreshRequestEvent()) sys.stdin = self.stdin self.reevaluating = False @@ -1405,6 +1418,7 @@ def compress_paste_event(paste_event): else: return None +#TODO this needs some work to function again and be useful for embedding def simple_repl(): refreshes = [] def request_refresh(): diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 15018dff1..815b5ebf1 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -11,7 +11,7 @@ def skip(f): from curtsies.formatstringarray import FormatStringTest, fsarray from curtsies.fmtfuncs import * -from curtsies.events import RefreshRequestEvent +from bpython.curtsiesfrontend.events import RefreshRequestEvent from bpython import config from bpython.curtsiesfrontend.repl import Repl @@ -89,8 +89,8 @@ def output_to_repl(repl): sys.stdout, sys.stderr = old_out, old_err class TestCurtsiesRewindRedraw(TestCurtsiesPainting): - def refresh(self, when='now'): - self.refresh_requests.append(RefreshRequestEvent(when=when)) + def refresh(self): + self.refresh_requests.append(RefreshRequestEvent()) def send_refreshes(self): while self.refresh_requests: From a91e44abf4219b2c16129f91397afcd941f4f55f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 26 Oct 2014 15:24:40 -0400 Subject: [PATCH 0105/1650] bump version to ensure input reactor in curtsies --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 685894e90..9e67146fe 100755 --- a/setup.py +++ b/setup.py @@ -153,7 +153,7 @@ def initialize_options(self): if sys.version_info[:2] >= (2, 6): # curtsies only supports 2.6 and onwards - extras_require['curtsies'] = ['curtsies >=0.1.11, <0.2.0', 'greenlet'] + extras_require['curtsies'] = ['curtsies >=0.1.15, <0.2.0', 'greenlet'] extras_require['watch'] = ['watchdog'] packages.append("bpython.curtsiesfrontend") entry_points['console_scripts'].append( From 42f3e540bdbd33722d928ca7b760bd68399d79c0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 26 Oct 2014 16:08:41 -0400 Subject: [PATCH 0106/1650] improve formatting --- bpython/curtsies.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 4ba670b97..ec9a30a0e 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -3,9 +3,7 @@ import code import logging import sys -import time from optparse import Option -from itertools import izip import curtsies import curtsies.window @@ -22,16 +20,17 @@ logger = logging.getLogger(__name__) -repl = None # global for `from bpython.curtsies import repl` -#WARNING Will be a problem if more than one repl is ever instantiated this way +repl = None # global for `from bpython.curtsies import repl` +# WARNING Will be a problem if more than one repl is ever instantiated this way + def main(args=None, locals_=None, banner=None): config, options, exec_args = bpargs.parse(args, ( 'curtsies options', None, [ Option('--log', '-L', action='store_true', - help=_("log debug messages to bpython.log")), + help=_("log debug messages to bpython.log")), Option('--type', '-t', action='store_true', - help=_("enter lines of file as though interactively typed")), + help=_("enter lines of file as though interactively typed")), ])) if options.log: handler = logging.FileHandler(filename='bpython.log') @@ -63,7 +62,7 @@ def main(args=None, locals_=None, banner=None): if not options.interactive: raise SystemExit(exit_value) else: - sys.path.insert(0, '') # expected for interactive sessions (vanilla python does it) + sys.path.insert(0, '') # expected for interactive sessions (vanilla python does it) print(bpargs.version_banner()) mainloop(config, locals_, banner, interp, paste, interactive=(not exec_args)) @@ -123,7 +122,7 @@ def process_event(e): if paste: process_event(paste) - process_event(None) # do a display before waiting for first event + process_event(None) # do a display before waiting for first event for _ in find_iterator: e = input_generator.send(0) if e is not None: From 6d31d46e0ab2cc1ab5a1d8ad950b7ed18acc8f8a Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Tue, 28 Oct 2014 13:20:00 +0100 Subject: [PATCH 0107/1650] Add a fallthrough sourcecodenotfound message --- bpython/repl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/repl.py b/bpython/repl.py index 13c06fb5c..6b7d8f6d8 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -594,6 +594,7 @@ def get_source_of_current_name(self): current name in the current input line. Throw `SourceNotFound` if the source cannot be found.""" obj = self.current_func + msg = "Unexpected error retrieving source code for %s" % (obj,) try: if obj is None: line = self.current_line From 7c274fe0ee9c69476707f7b723a1a3a335738efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Tue, 28 Oct 2014 15:02:39 +0100 Subject: [PATCH 0108/1650] Fix indentation error. Was introduced in e18e43787bfe26f58c503cc7738c7336409f1150. --- bpython/repl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 6b7d8f6d8..b767189d7 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -594,7 +594,6 @@ def get_source_of_current_name(self): current name in the current input line. Throw `SourceNotFound` if the source cannot be found.""" obj = self.current_func - msg = "Unexpected error retrieving source code for %s" % (obj,) try: if obj is None: line = self.current_line @@ -602,7 +601,7 @@ def get_source_of_current_name(self): raise SourceNotFound("Nothing to get source of") if inspection.is_eval_safe_name(line): obj = self.get_object(line) - return inspect.getsource(obj) + return inspect.getsource(obj) except (AttributeError, NameError), e: msg = "Cannot get source: " + str(e) except IOError, e: From 934c511be5e251b762a69e724118a2ca8cfdeafb Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 22 Nov 2014 15:52:46 -0500 Subject: [PATCH 0109/1650] some tests for getting source --- bpython/test/test_repl.py | 49 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 86692dc91..7eaa5315d 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,8 +1,12 @@ +import collections +from itertools import islice import os +import socket import sys import unittest -from itertools import islice + from mock import Mock, MagicMock + try: from unittest import skip except ImportError: @@ -13,6 +17,7 @@ def skip(f): from bpython import config, repl, cli, autocomplete + def setup_config(conf): config_struct = config.Struct() config.loadini(config_struct, os.devnull) @@ -253,6 +258,48 @@ def test_nonexistent_name(self): self.assertFalse(self.repl.get_args()) +class TestGetSource(unittest.TestCase): + def setUp(self): + self.repl = FakeRepl() + + def set_input_line(self, line): + """Set current input line of the test REPL.""" + self.repl.current_line = line + self.repl.cursor_offset = len(line) + + def assert_get_source_error_for_current_function(self, func, msg): + self.repl.current_func = func + self.assertRaises(repl.SourceNotFound, self.repl.get_source_of_current_name) + try: + self.repl.get_source_of_current_name() + except repl.SourceNotFound as e: + self.assertEqual(e.args[0], msg) + + def test_current_function(self): + self.set_input_line('INPUTLINE') + self.repl.current_func = collections.MutableSet.add + self.assertTrue("Add an element." in self.repl.get_source_of_current_name()) + + self.assert_get_source_error_for_current_function( + collections.defaultdict.copy, "No source code found for INPUTLINE") + + self.assert_get_source_error_for_current_function( + collections.defaultdict, "could not find class definition") + + self.assert_get_source_error_for_current_function( + [], "No source code found for INPUTLINE") + + self.assert_get_source_error_for_current_function( + list.pop, "No source code found for INPUTLINE") + + def test_current_line(self): + self.repl.interp.locals['a'] = socket.socket + self.set_input_line('a') + self.assertTrue('dup(self)' in self.repl.get_source_of_current_name()) + +#TODO add tests for various failures without using current function + + class TestRepl(unittest.TestCase): def setInputLine(self, line): From d7b95d988a00c11d3e4cc66239e929ed0e071a2a Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 22 Nov 2014 15:57:07 -0500 Subject: [PATCH 0110/1650] deal with more things being logged by curtsies now --- bpython/curtsies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index ec9a30a0e..d0282b92f 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -41,7 +41,8 @@ def main(args=None, locals_=None, banner=None): logging.getLogger('bpython').addHandler(handler) logging.getLogger('bpython').propagate = False else: - logging.getLogger('bpython').setLevel(logging.WARNING) + logging.getLogger('bpython').setLevel(logging.ERROR) + logging.getLogger('curtsies').setLevel(logging.ERROR) interp = None paste = None From c131d617b09a2436e7e3685c565089061fd17f49 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 22 Nov 2014 19:39:24 -0500 Subject: [PATCH 0111/1650] deleting of lines super-paste feature --- bpython/curtsiesfrontend/repl.py | 98 +++++++++++++++++++++----- bpython/test/test_curtsies_painting.py | 1 + bpython/test/test_curtsies_repl.py | 14 ++-- bpython/test/test_interpreter.py | 13 ++++ 4 files changed, 100 insertions(+), 26 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index ae1604e53..3735d7080 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -461,8 +461,11 @@ def process_control_event(self, e): ctrl_char = compress_paste_event(e) if ctrl_char is not None: return self.process_event(ctrl_char) + simple_events = just_simple_events(e.events) + source = bad_empty_lines_removed(''.join(simple_events)) + with self.in_paste_mode(): - for ee in e.events: + for ee in source: if self.stdin.has_focus: self.stdin.process_event(ee) else: @@ -712,7 +715,9 @@ def send_session_to_external_editor(self, filename=None): for line in self.getstdout().split('\n')) text = self.send_to_external_editor(for_editor) lines = text.split('\n') - self.history = [line for line in lines if line[:4] != '### '] + from_editor = [line for line in lines if line[:4] != '### '] + source = bad_empty_lines_removed('\n'.join(from_editor)) + self.history = source.split('\n') self.reevaluate(insert_into_history=True) self.current_line = lines[-1][4:] self.cursor_offset = len(self.current_line) @@ -822,7 +827,7 @@ def push(self, line, insert_into_history=True): code_to_run = '\n'.join(self.buffer) logger.debug('running %r in interpreter', self.buffer) - c, code_will_parse = self.buffer_finished_will_parse() + c, code_will_parse = code_finished_will_parse('\n'.join(self.buffer)) self.saved_predicted_parse_error = not code_will_parse if c: logger.debug('finished - buffer cleared') @@ -834,21 +839,6 @@ def push(self, line, insert_into_history=True): self.coderunner.load_code(code_to_run) self.run_code_and_maybe_finish() - def buffer_finished_will_parse(self): - """Returns a tuple of whether the buffer could be complete and whether it will parse - - True, True means code block is finished and no predicted parse error - True, False means code block is finished because a parse error is predicted - False, True means code block is unfinished - False, False isn't possible - an predicted error makes code block done""" - try: - finished = bool(code.compile_command('\n'.join(self.buffer))) - code_will_parse = True - except (ValueError, SyntaxError, OverflowError): - finished = True - code_will_parse = False - return finished, code_will_parse - def run_code_and_maybe_finish(self, for_code=None): r = self.coderunner.run_code(for_code=for_code) if r: @@ -861,7 +851,6 @@ def run_code_and_maybe_finish(self, for_code=None): if err: indent = 0 - #TODO This should be printed ABOVE the error that just happened instead # or maybe just thrown away and not shown if self.current_stdouterr_line: @@ -1418,6 +1407,77 @@ def compress_paste_event(paste_event): else: return None +def just_simple_events(event_list): + simple_events = [] + for e in event_list: + if e in (u"", u"", u"", u"\n", u"\r"): # '\n' necessary for pastes + simple_events.append(u'\n') + elif isinstance(e, events.Event): + pass # ignore events + elif e == '': + simple_events.append(' ') + else: + simple_events.append(e) + return simple_events + +def code_finished_will_parse(s): + """Returns a tuple of whether the buffer could be complete and whether it will parse + + True, True means code block is finished and no predicted parse error + True, False means code block is finished because a parse error is predicted + False, True means code block is unfinished + False, False isn't possible - an predicted error makes code block done""" + try: + finished = bool(code.compile_command(s)) + code_will_parse = True + except (ValueError, SyntaxError, OverflowError): + finished = True + code_will_parse = False + return finished, code_will_parse + +def bad_empty_lines_removed(s): + """Removes empty lines that would cause unfinished input to be evaluated""" + # If there's a syntax error followed by an empty line, remove the empty line + lines = s.split('\n') + #TODO this should be our interpreter object making this decision so it + # can be compiler directive (__future__ statement) -aware + #TODO specifically catch IndentationErrors instead of any syntax errors + + current_block = [] + complete_blocks = [] + for i, line in enumerate(s.split('\n')): + current_block.append(line) + could_be_finished, valid = code_finished_will_parse('\n'.join(current_block)) + if could_be_finished and valid: + complete_blocks.append(current_block) + current_block = [] + continue + elif could_be_finished and not valid: + if complete_blocks: + complete_blocks[-1].extend(current_block) + current_block = complete_blocks.pop() + if len(current_block) < 2: + return s #TODO return partial result instead of giving up + last_line = current_block.pop(len(current_block) - 2) + assert not last_line, last_line + new_finished, new_valid = code_finished_will_parse('\n'.join(current_block)) + if new_valid and new_finished: + complete_blocks.append(current_block) + current_block = [] + elif new_valid: + continue + else: + return s #TODO return partial result instead of giving up + + else: + return s #TODO return partial result instead of giving up + else: + continue + return '\n'.join(['\n'.join(block) + for block in complete_blocks + [current_block] + if block]) + + #TODO this needs some work to function again and be useful for embedding def simple_repl(): refreshes = [] diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 815b5ebf1..9a3823db4 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -1,4 +1,5 @@ # coding: utf8 +from __future__ import unicode_literals import sys import os from contextlib import contextmanager diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 00652fa78..344cfcb20 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -33,19 +33,19 @@ class TestCurtsiesRepl(unittest.TestCase): def setUp(self): self.repl = create_repl() - def test_buffer_finished_will_parse(self): + def test_code_finished_will_parse(self): self.repl.buffer = ['1 + 1'] - self.assertTrue(self.repl.buffer_finished_will_parse(), (True, True)) + self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True)) self.repl.buffer = ['def foo(x):'] - self.assertTrue(self.repl.buffer_finished_will_parse(), (False, True)) + self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (False, True)) self.repl.buffer = ['def foo(x)'] - self.assertTrue(self.repl.buffer_finished_will_parse(), (True, False)) + self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, False)) self.repl.buffer = ['def foo(x):', 'return 1'] - self.assertTrue(self.repl.buffer_finished_will_parse(), (True, False)) + self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, False)) self.repl.buffer = ['def foo(x):', ' return 1'] - self.assertTrue(self.repl.buffer_finished_will_parse(), (True, True)) + self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True)) self.repl.buffer = ['def foo(x):', ' return 1', ''] - self.assertTrue(self.repl.buffer_finished_will_parse(), (True, True)) + self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True)) def test_external_communication(self): self.assertEqual(type(self.repl.help_text()), type(b'')) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index bb2cc29cb..56a8806e5 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -1,6 +1,7 @@ import unittest from bpython.curtsiesfrontend import interpreter +from bpython.curtsiesfrontend.repl import bad_empty_lines_removed from curtsies.fmtfuncs import * class TestInterpreter(unittest.TestCase): @@ -40,3 +41,15 @@ def g(): self.assertEquals(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) + +class TestPreprocessing(unittest.TestCase): + def test_bad_empty_lines_removed(self): + self.assertEqual(bad_empty_lines_removed("def foo():\n" + " return 1\n" + "\n" + " pass\n"), + "def foo():\n" + " return 1\n" + " pass\n") + + From c2b4fbe46c70c5c1fe85f61502b70d77bc8414ca Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 23 Nov 2014 00:10:40 -0500 Subject: [PATCH 0112/1650] fix travis build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7c3c9c3a1..fbb52c98d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: install: - "python setup.py install" - - "pip install curtsies greenlet pygments requests" + - "pip install 'curtsies<0.2.0' greenlet pygments requests" script: - cd build/lib/ From 57d4ee0c72e0551fdec7c82ebe5900eb128bab60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Fri, 12 Dec 2014 22:50:08 +0100 Subject: [PATCH 0113/1650] Sprinkle some assertIn/assertNotIn into tests. Refs #428. --- .travis.yml | 2 +- bpython/test/test_crashers.py | 11 ++++----- bpython/test/test_manual_readline.py | 6 ++--- bpython/test/test_repl.py | 34 +++++++++++++--------------- setup.py | 2 +- 5 files changed, 25 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index fbb52c98d..d45d69954 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: install: - "python setup.py install" - - "pip install 'curtsies<0.2.0' greenlet pygments requests" + - "pip install 'curtsies<0.2.0' greenlet pygments requests unittest2" script: - cd build/lib/ diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 533378ca1..a28fdf134 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -5,13 +5,11 @@ import sys import termios import textwrap -import unittest try: - from unittest import skip + import unittest2 as unittest except ImportError: - def skip(f): - return lambda self: None + import unittest try: from twisted.internet import reactor @@ -103,14 +101,13 @@ def spam(a, (b, c)): return self.run_bpython(input).addCallback(self.check_no_traceback) def check_no_traceback(self, data): - tb = data[data.find("Traceback"):] - self.assertTrue("Traceback" not in data, tb) + self.assertNotIn("Traceback", data) if reactor is not None: class CursesCrashersTest(TrialTestCase, CrashersTest): backend = "cli" - @skip("take 6 seconds, and Simon says we can skip them") + @unittest.skip("take 6 seconds, and Simon says we can skip them") class UrwidCrashersTest(TrialTestCase, CrashersTest): backend = "urwid" diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 5cfdcc6c7..f0bbf4719 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -222,7 +222,7 @@ def setUp(self): def test_seq(self): f = lambda cursor_offset, line: ('hi', 2) self.edits.add('a', f) - self.assertTrue('a' in self.edits) + self.assertIn('a', self.edits) self.assertEqual(self.edits['a'], f) self.assertEqual(self.edits.call('a', cursor_offset=3, line='hello'), ('hi', 2)) @@ -245,12 +245,12 @@ def test_config(self): f = lambda cursor_offset, line: ('hi', 2) g = lambda cursor_offset, line: ('hey', 3) self.edits.add_config_attr('att', f) - self.assertFalse('att' in self.edits) + self.assertNotIn('att', self.edits) class config: att = 'c' key_dispatch = {'c': 'c'} configured_edits = self.edits.mapping_with_config(config, key_dispatch) self.assertTrue(configured_edits.__contains__, 'c') - self.assertFalse('c' in self.edits) + self.assertNotIn('c', self.edits) self.assertRaises(NotImplementedError, configured_edits.add_config_attr, 'att2', g) self.assertRaises(NotImplementedError, diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 7eaa5315d..3b246f29a 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -3,15 +3,13 @@ import os import socket import sys -import unittest from mock import Mock, MagicMock try: - from unittest import skip + import unittest2 as unittest except ImportError: - def skip(f): - return lambda self: None + import unittest py3 = (sys.version_info[0] == 3) @@ -101,7 +99,7 @@ def test_append(self): self.assertEqual(self.history.back(), 'print "foo\n"') - @skip("I don't understand this test") + @unittest.skip("I don't understand this test") def test_enter(self): self.history.enter('#lastnumber!') @@ -278,7 +276,7 @@ def assert_get_source_error_for_current_function(self, func, msg): def test_current_function(self): self.set_input_line('INPUTLINE') self.repl.current_func = collections.MutableSet.add - self.assertTrue("Add an element." in self.repl.get_source_of_current_name()) + self.assertIn("Add an element.", self.repl.get_source_of_current_name()) self.assert_get_source_error_for_current_function( collections.defaultdict.copy, "No source code found for INPUTLINE") @@ -295,7 +293,7 @@ def test_current_function(self): def test_current_line(self): self.repl.interp.locals['a'] = socket.socket self.set_input_line('a') - self.assertTrue('dup(self)' in self.repl.get_source_of_current_name()) + self.assertIn('dup(self)', self.repl.get_source_of_current_name()) #TODO add tests for various failures without using current function @@ -334,7 +332,7 @@ def test_simple_global_complete(self): self.assertEqual(self.repl.matches_iter.matches, ['def', 'del', 'delattr(', 'dict(', 'dir(', 'divmod(']) - @skip("disabled while non-simple completion is disabled") + @unittest.skip("disabled while non-simple completion is disabled") def test_substring_global_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SUBSTRING}) self.setInputLine("time") @@ -344,7 +342,7 @@ def test_substring_global_complete(self): self.assertEqual(self.repl.completer.matches, ['RuntimeError(', 'RuntimeWarning(']) - @skip("disabled while non-simple completion is disabled") + @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_global_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.FUZZY}) self.setInputLine("doc") @@ -368,7 +366,7 @@ def test_simple_attribute_complete(self): self.assertEqual(self.repl.matches_iter.matches, ['Foo.bar']) - @skip("disabled while non-simple completion is disabled") + @unittest.skip("disabled while non-simple completion is disabled") def test_substring_attribute_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SUBSTRING}) self.setInputLine("Foo.az") @@ -382,7 +380,7 @@ def test_substring_attribute_complete(self): self.assertEqual(self.repl.completer.matches, ['Foo.baz']) - @skip("disabled while non-simple completion is disabled") + @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_attribute_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.FUZZY}) self.setInputLine("Foo.br") @@ -412,7 +410,7 @@ def test_file_should_not_appear_in_complete(self): self.setInputLine("_") self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.matches_iter,'matches')) - self.assertTrue('__file__' not in self.repl.matches_iter.matches) + self.assertNotIn('__file__', self.repl.matches_iter.matches) class TestCliRepl(unittest.TestCase): @@ -465,7 +463,7 @@ def test_simple_tab_complete(self): self.repl.complete.assert_called_with(tab=True) self.assertEqual(self.repl.s, "foobar") - @skip("disabled while non-simple completion is disabled") + @unittest.skip("disabled while non-simple completion is disabled") def test_substring_tab_complete(self): self.repl.s = "bar" self.repl.config.autocomplete_mode = autocomplete.FUZZY @@ -474,7 +472,7 @@ def test_substring_tab_complete(self): self.repl.tab() self.assertEqual(self.repl.s, "foofoobar") - @skip("disabled while non-simple completion is disabled") + @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_tab_complete(self): self.repl.s = "br" self.repl.config.autocomplete_mode = autocomplete.FUZZY @@ -509,7 +507,7 @@ def test_back_parameter(self): self.assertTrue(self.repl.s, "previtem") # Attribute Tests - @skip("disabled while non-simple completion is disabled") + @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_attribute_tab_complete(self): """Test fuzzy attribute with no text""" self.repl.s = "Foo." @@ -518,7 +516,7 @@ def test_fuzzy_attribute_tab_complete(self): self.repl.tab() self.assertEqual(self.repl.s, "Foo.foobar") - @skip("disabled while non-simple completion is disabled") + @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_attribute_tab_complete2(self): """Test fuzzy attribute with some text""" self.repl.s = "Foo.br" @@ -538,14 +536,14 @@ def test_simple_expand(self): self.repl.tab() self.assertEqual(self.repl.s, "foo") - @skip("disabled while non-simple completion is disabled") + @unittest.skip("disabled while non-simple completion is disabled") def test_substring_expand_forward(self): self.repl.config.autocomplete_mode = autocomplete.SUBSTRING self.repl.s = "ba" self.repl.tab() self.assertEqual(self.repl.s, "bar") - @skip("disabled while non-simple completion is disabled") + @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_expand(self): pass diff --git a/setup.py b/setup.py index 9e67146fe..936974275 100755 --- a/setup.py +++ b/setup.py @@ -183,7 +183,7 @@ def initialize_options(self): 'requests' ], extras_require = extras_require, - tests_require = ['mock'], + tests_require = ['mock', 'unittest2'], packages = packages, data_files = data_files, package_data = { From 6966e2e20643f3712681bc8742347cb213fb1bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Fri, 12 Dec 2014 23:06:23 +0100 Subject: [PATCH 0114/1650] Add missing unittest2 import. --- bpython/test/test_manual_readline.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index f0bbf4719..58b6550e8 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -1,5 +1,10 @@ +try: + import unittest2 as unittest +except ImportError: + import unittest + from bpython.curtsiesfrontend.manual_readline import * -import unittest + class TestManualReadline(unittest.TestCase): def setUp(self): From f20d6d56ce3513fb5763d939943fe5565e4102cb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 23 Dec 2014 15:24:49 +0100 Subject: [PATCH 0115/1650] Depend on PyOpenSSL for SNI support (fixes #430) This is only required for Python 2.X on Mac OS X. Signed-off-by: Sebastian Ramacher --- setup.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 936974275..ac6c2cb20 100755 --- a/setup.py +++ b/setup.py @@ -135,6 +135,11 @@ def initialize_options(self): ] data_files.extend(man_pages) +install_requires = [ + 'pygments', + 'requests' +] + extras_require = { 'urwid' : ['urwid'] } @@ -161,6 +166,10 @@ def initialize_options(self): if not using_setuptools: scripts.append('data/bpython-curtsies') +if sys.version_info[0] == 2 and sys.platform == "darwin": + # need PyOpenSSL for SNI support (only 2.X and on Darwin) + install_requires.append('PyOpenSSL') + # translations mo_files = list() for language in os.listdir(translations_dir): @@ -178,10 +187,7 @@ def initialize_options(self): url = "http://www.bpython-interpreter.org/", long_description = """bpython is a fancy interface to the Python interpreter for Unix-like operating systems.""", - install_requires = [ - 'pygments', - 'requests' - ], + install_requires = install_requires, extras_require = extras_require, tests_require = ['mock', 'unittest2'], packages = packages, From f977d810274916c07aa0d207e870041c3f459dae Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 23 Dec 2014 15:33:40 +0100 Subject: [PATCH 0116/1650] Also depend on ndg-httpsclient and pyasn1 Signed-off-by: Sebastian Ramacher --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index ac6c2cb20..f86cb1f8a 100755 --- a/setup.py +++ b/setup.py @@ -168,7 +168,11 @@ def initialize_options(self): if sys.version_info[0] == 2 and sys.platform == "darwin": # need PyOpenSSL for SNI support (only 2.X and on Darwin) + # list of packages taken from + # https://github.com/kennethreitz/requests/blob/master/requests/packages/urllib3/contrib/pyopenssl.py install_requires.append('PyOpenSSL') + install_requires.append('ndg-httpsclient') + install_requires.append('pyasn1') # translations mo_files = list() From 4091bdc5f058b1c162f5e9fcbed0f21e0e368bf9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 6 Jan 2015 20:19:46 +0100 Subject: [PATCH 0117/1650] Install dependencies first Signed-off-by: Sebastian Ramacher --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fbb52c98d..afe051f82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,8 @@ python: - "3.4" install: - - "python setup.py install" - "pip install 'curtsies<0.2.0' greenlet pygments requests" + - "python setup.py install" script: - cd build/lib/ From 97c2b067cce8b43ed033e454ba086bbead09066d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 6 Jan 2015 20:33:12 +0100 Subject: [PATCH 0118/1650] Install more dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … and comment why they are needed. This enables building of the documentation and translations. Signed-off-by: Sebastian Ramacher --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index afe051f82..cf2c45c3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,11 @@ python: - "3.4" install: - - "pip install 'curtsies<0.2.0' greenlet pygments requests" + - "pip install setuptools" + - "pip install pygments requests" # core dependencies + - "pip install 'curtsies<0.2.0' greenlet" # curtsies specific dependencies + - "pip install babel" # translation specific dependencies + - "pip install sphinx" # documentation specific dependencies - "python setup.py install" script: From 0db42ff67b0e8eb5dcd878875f5ca065891a4c6e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 6 Jan 2015 14:33:34 -0500 Subject: [PATCH 0119/1650] document get_completer autocompletion helper --- bpython/autocomplete.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 914eef150..000b9fcdf 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -60,15 +60,26 @@ def after_last_dot(name): return name.rstrip('.').rsplit('.')[-1] -def get_completer(cursor_offset, current_line, locals_, argspec, full_code, mode, complete_magic_methods): +def get_completer(cursor_offset, current_line, locals_, argspec, current_block, + mode, complete_magic_methods): """Returns a list of matches and a class for what kind of completion is happening If no completion type is relevant, returns None, None - argspec is an output of inspect.getargspec + Params: + cursor_offset is the current cursor column + current_line is a string of the current line + locals_ is a dictionary of the environment + argspec is an inspect.ArgSpec instance for the current function where + the cursor is + current_block is the possibly multiline not-yet-evaluated block of + code which the current line is part of + mode is one of SIMPLE, SUBSTRING or FUZZY - ways to find matches + complete_magic_methods is a bool of whether we ought to complete + double underscore methods like __len__ in method signatures """ - kwargs = {'locals_':locals_, 'argspec':argspec, 'full_code':full_code, + kwargs = {'locals_':locals_, 'argspec':argspec, 'current_block':current_block, 'mode':mode, 'complete_magic_methods':complete_magic_methods} # mutually exclusive if matches: If one of these returns [], try the next one @@ -220,11 +231,11 @@ def format(cls, match): class MagicMethodCompletion(BaseCompletionType): locate = staticmethod(lineparts.current_method_definition_name) @classmethod - def matches(cls, cursor_offset, line, full_code, **kwargs): + def matches(cls, cursor_offset, line, current_block, **kwargs): r = cls.locate(cursor_offset, line) if r is None: return None - if 'class' not in full_code: + if 'class' not in current_block: return None start, end, word = r return [name for name in MAGIC_METHODS if name.startswith(word)] From 9119e5355438e97a1c4da1f03e3e5eee60927c6a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 6 Jan 2015 20:37:37 +0100 Subject: [PATCH 0120/1650] Install unittests2 for Python 2.6 Signed-off-by: Sebastian Ramacher --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index cf2c45c3c..b5f6395e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ install: - "pip install 'curtsies<0.2.0' greenlet" # curtsies specific dependencies - "pip install babel" # translation specific dependencies - "pip install sphinx" # documentation specific dependencies + - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi - "python setup.py install" script: From ab54341086c6335fe816ce9576c74174d00e15bc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 6 Jan 2015 20:38:26 +0100 Subject: [PATCH 0121/1650] Reformat --- .travis.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index b5f6395e8..874278737 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,13 +7,17 @@ python: - "3.4" install: - - "pip install setuptools" - - "pip install pygments requests" # core dependencies - - "pip install 'curtsies<0.2.0' greenlet" # curtsies specific dependencies - - "pip install babel" # translation specific dependencies - - "pip install sphinx" # documentation specific dependencies + - pip install setuptools + # core dependencies + - pip install pygments requests + # curtsies specific dependencies + - pip install 'curtsies<0.2.0' greenlet + # translation specific dependencies + - pip install babel + # documentation specific dependencies + - pip install sphinx - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi - - "python setup.py install" + - python setup.py install script: - cd build/lib/ From cd8d7344a8480b2a5b0afaccec9d2177c67aa654 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 6 Jan 2015 20:43:58 +0100 Subject: [PATCH 0122/1650] Fix title underline Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/community.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/community.rst b/doc/sphinx/source/community.rst index 2b946a48d..2d1be5c98 100644 --- a/doc/sphinx/source/community.rst +++ b/doc/sphinx/source/community.rst @@ -16,7 +16,7 @@ from Europe and when you get to the channel during our nighttime you might have to wait a while for a response. Mailing List ------------ +------------ We have a mailing list at `google groups `_. You can post questions there and releases are announced on the mailing list. From d6de41b9506fca3424337e23965295cbff1d6a38 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 6 Jan 2015 20:58:43 +0100 Subject: [PATCH 0123/1650] Only depend on unittest2 for Python 2.X with X < 7 Signed-off-by: Sebastian Ramacher --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f86cb1f8a..cfa181e00 100755 --- a/setup.py +++ b/setup.py @@ -174,6 +174,10 @@ def initialize_options(self): install_requires.append('ndg-httpsclient') install_requires.append('pyasn1') +tests_require = ['mock'] +if sys.version_info[0] == 2 and sys.version_info[1] < 7: + tests_require.append('unittest2') + # translations mo_files = list() for language in os.listdir(translations_dir): @@ -193,7 +197,7 @@ def initialize_options(self): interpreter for Unix-like operating systems.""", install_requires = install_requires, extras_require = extras_require, - tests_require = ['mock', 'unittest2'], + tests_require = tests_require, packages = packages, data_files = data_files, package_data = { From a29140bb5cebdf8dfd230f8d16c96599a2f6d191 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 6 Jan 2015 21:09:08 +0100 Subject: [PATCH 0124/1650] setuptools only setup.py Signed-off-by: Sebastian Ramacher --- setup.py | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/setup.py b/setup.py index cfa181e00..8fcb35657 100755 --- a/setup.py +++ b/setup.py @@ -7,22 +7,14 @@ import sys from distutils.command.build import build - -from bpython import __version__, package_dir - +from setuptools import setup +from setuptools.command.install import install as _install try: - from setuptools import setup - from setuptools.command.install import install as _install - using_setuptools = True + from setuptools.command.build_py import build_py_2to3 as build_py except ImportError: - from distutils.core import setup - from distutils.command.install import install as _install - using_setuptools = False + from setuptools.command.build_py import build_py -try: - from distutils.command.build_py import build_py_2to3 as build_py -except ImportError: - from distutils.command.build_py import build_py +from bpython import __version__, package_dir try: from babel.messages.frontend import compile_catalog as _compile_catalog @@ -153,9 +145,6 @@ def initialize_options(self): ] } -scripts = [] if using_setuptools else ['data/bpython', - 'data/bpython-urwid'] - if sys.version_info[:2] >= (2, 6): # curtsies only supports 2.6 and onwards extras_require['curtsies'] = ['curtsies >=0.1.15, <0.2.0', 'greenlet'] @@ -163,8 +152,6 @@ def initialize_options(self): packages.append("bpython.curtsiesfrontend") entry_points['console_scripts'].append( 'bpython-curtsies = bpython.curtsies:main [curtsies]') - if not using_setuptools: - scripts.append('data/bpython-curtsies') if sys.version_info[0] == 2 and sys.platform == "darwin": # need PyOpenSSL for SNI support (only 2.X and on Darwin) @@ -206,7 +193,6 @@ def initialize_options(self): 'bpython.test': ['test.config', 'test.theme'] }, entry_points = entry_points, - scripts = scripts, cmdclass = cmdclass, test_suite = 'bpython.test' ) From 12a31f622422ce8931265cc5784b0ab3f29dd6ed Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 6 Jan 2015 21:20:53 +0100 Subject: [PATCH 0125/1650] Clean up scripts in data Signed-off-by: Sebastian Ramacher --- data/bpython | 6 ------ data/bpython-curtsies | 6 ------ data/bpython-urwid | 6 ------ 3 files changed, 18 deletions(-) delete mode 100755 data/bpython delete mode 100755 data/bpython-curtsies delete mode 100755 data/bpython-urwid diff --git a/data/bpython b/data/bpython deleted file mode 100755 index 780e7a0de..000000000 --- a/data/bpython +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -import sys -from bpython.cli import main - -sys.exit(main()) diff --git a/data/bpython-curtsies b/data/bpython-curtsies deleted file mode 100755 index ac4ced087..000000000 --- a/data/bpython-curtsies +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -import sys -from bpython.curtsies import main - -sys.exit(main()) diff --git a/data/bpython-urwid b/data/bpython-urwid deleted file mode 100755 index 3d8f01b3e..000000000 --- a/data/bpython-urwid +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -import sys -from bpython.urwid import main - -sys.exit(main()) From 2d34994214514d6a721ca702a5c480cd864f7b2d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 6 Jan 2015 15:24:03 -0500 Subject: [PATCH 0126/1650] only call update_completion once when adding a character --- bpython/curtsiesfrontend/repl.py | 14 ++++++++------ bpython/repl.py | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3735d7080..c31eba82f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -753,9 +753,12 @@ def add_normal_character(self, char): if self.incremental_search_mode: self.add_to_incremental_search(char) else: - self.current_line = (self.current_line[:self.cursor_offset] + - char + - self.current_line[self.cursor_offset:]) + self._set_current_line((self.current_line[:self.cursor_offset] + + char + + self.current_line[self.cursor_offset:]), + update_completion=False, + reset_rl_history=False, + clear_special_mode=False) self.cursor_offset += 1 if self.config.cli_trim_prompts and self.current_line.startswith(self.ps1): self.current_line = self.current_line[4:] @@ -1232,14 +1235,13 @@ def _get_cursor_offset(self): def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=False, clear_special_mode=True): if self._cursor_offset == offset: return - if update_completion: - self.update_completion() if reset_rl_history: self.rl_history.reset() if clear_special_mode: self.incremental_search_mode = None self._cursor_offset = offset - self.update_completion() + if update_completion: + self.update_completion() self.unhighlight_paren() cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, "The current cursor offset from the front of the line") diff --git a/bpython/repl.py b/bpython/repl.py index b767189d7..81900aec9 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -629,7 +629,7 @@ def set_docstring(self): self.docstring = None def complete(self, tab=False): - """Construct a full list of possible completions and construct and + """Construct a full list of possible completions and display them in a window. Also check if there's an available argspec (via the inspect module) and bang that on top of the completions too. The return value is whether the list_win is visible or not. From 8c11b561e2a5f44d085cf93b6f1c2b950d97920f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 6 Jan 2015 21:37:34 +0100 Subject: [PATCH 0127/1650] Just use use_2to3 instead of playing with commands Signed-off-by: Sebastian Ramacher --- setup.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 8fcb35657..1818471ae 100755 --- a/setup.py +++ b/setup.py @@ -9,10 +9,6 @@ from distutils.command.build import build from setuptools import setup from setuptools.command.install import install as _install -try: - from setuptools.command.build_py import build_py_2to3 as build_py -except ImportError: - from setuptools.command.build_py import build_py from bpython import __version__, package_dir @@ -44,7 +40,7 @@ def run(self): self.run_command('build') _install.run(self) -cmdclass = dict(build_py=build_py, build=build, install=install) +cmdclass = dict(build=build, install=install) translations_dir = os.path.join(package_dir, 'translations') # localization options @@ -194,7 +190,8 @@ def initialize_options(self): }, entry_points = entry_points, cmdclass = cmdclass, - test_suite = 'bpython.test' + test_suite = 'bpython.test', + use_2to3 = True ) # vim: fileencoding=utf-8 sw=4 ts=4 sts=4 ai et sta From d441dd54b4d383c4a469aed3322f7184fdc7af57 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 02:56:04 +0100 Subject: [PATCH 0128/1650] Move curtsies.bpythonparse to bpython.curtsiesfrontent.parse This is the bpython part of curtsies #44. Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/interpreter.py | 4 +- bpython/curtsiesfrontend/parse.py | 90 +++++++++++++++++++++++++ bpython/curtsiesfrontend/repl.py | 4 +- bpython/curtsiesfrontend/replpainter.py | 2 +- bpython/test/test_curtsies_parser.py | 18 +++++ 5 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 bpython/curtsiesfrontend/parse.py create mode 100644 bpython/test/test_curtsies_parser.py diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index a030ea12a..a980079c7 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -3,7 +3,7 @@ import sys from pygments.token import * from pygments.formatter import Formatter -from curtsies.bpythonparse import parse +from bpython.curtsiesfrontend.parse import parse from codeop import CommandCompiler from pygments.lexers import get_lexer_by_name @@ -27,7 +27,7 @@ Name.Function:'d', Name.Class:'d', } - + class BPythonFormatter(Formatter): """This is subclassed from the custom formatter for bpython. Its format() method receives the tokensource diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py new file mode 100644 index 000000000..37f80b33e --- /dev/null +++ b/bpython/curtsiesfrontend/parse.py @@ -0,0 +1,90 @@ + +from bpython.formatter import BPythonFormatter +from bpython._py3compat import PythonLexer +from bpython.config import Struct, loadini, default_config_path + +from curtsies.termformatconstants import FG_COLORS, BG_COLORS, colors +from curtsies.formatstring import fmtstr, FmtStr + +from pygments import format +from functools import partial + +import re + +cnames = dict(list(zip('krgybmcwd', colors + ('default',)))) + +def func_for_letter(l, default='k'): + """Returns FmtStr constructor for a bpython-style color code""" + if l == 'd': + l = default + elif l == 'D': + l = default.upper() + return partial(fmtstr, fg=cnames[l.lower()], bold=(l.lower() != l)) + +def color_for_letter(l, default='k'): + if l == 'd': + l = default + return cnames[l.lower()] + +def parse(s): + """Returns a FmtStr object from a bpython-formatted colored string""" + rest = s + stuff = [] + while True: + if not rest: + break + d, rest = peel_off_string(rest) + stuff.append(d) + return (sum([fs_from_match(d) for d in stuff[1:]], fs_from_match(stuff[0])) + if len(stuff) > 0 + else FmtStr()) + +def fs_from_match(d): + atts = {} + if d['fg']: + + # this isn't according to spec as I understand it + if d['fg'] != d['fg'].lower(): + d['bold'] = True + #TODO figure out why boldness isn't based on presence of \x02 + + color = cnames[d['fg'].lower()] + if color != 'default': + atts['fg'] = FG_COLORS[color] + if d['bg']: + if d['bg'] == 'I': + color = colors[(colors.index(color) + (len(colors) // 2)) % len(colors)] # hack for finding the "inverse" + else: + color = cnames[d['bg'].lower()] + if color != 'default': + atts['bg'] = BG_COLORS[color] + if d['bold']: + atts['bold'] = True + return fmtstr(d['string'], **atts) + +def peel_off_string(s): + r""" + >>> r = peel_off_string('\x01RI\x03]\x04asdf') + >>> r == ({'bg': 'I', 'string': ']', 'fg': 'R', 'colormarker': '\x01RI', 'bold': ''}, 'asdf') + True + """ + p = r"""(?P\x01 + (?P[krgybmcwdKRGYBMCWD]?) + (?P[krgybmcwdKRGYBMCWDI]?)?) + (?P\x02?) + \x03 + (?P[^\x04]*) + \x04 + (?P.*) + """ + m = re.match(p, s, re.VERBOSE | re.DOTALL) + assert m, repr(s) + d = m.groupdict() + rest = d['rest'] + del d['rest'] + return d, rest + +def string_to_fmtstr(x): + config = Struct() + loadini(config, default_config_path()) + return parse(format(PythonLexer().get_tokens(x), BPythonFormatter(config.color_scheme))) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3735d7080..7eed90c81 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -23,8 +23,6 @@ import curtsies from curtsies import FSArray, fmtstr, FmtStr, Termmode -from curtsies.bpythonparse import parse as bpythonparse -from curtsies.bpythonparse import func_for_letter, color_for_letter from curtsies import fmtfuncs from curtsies import events @@ -44,6 +42,8 @@ from bpython.curtsiesfrontend.interaction import StatusBar from bpython.curtsiesfrontend.manual_readline import edit_keys from bpython.curtsiesfrontend import events as bpythonevents +from bpython.curtsiesfrontend.parse import parse as bpythonparse +from bpython.curtsiesfrontend.parse import func_for_letter, color_for_letter #TODO other autocomplete modes (also fix in other bpython implementations) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index e24467657..b5141cefa 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -2,10 +2,10 @@ import logging from curtsies import fsarray, fmtstr -from curtsies.bpythonparse import func_for_letter from curtsies.formatstring import linesplit from curtsies.fmtfuncs import bold +from bpython.curtsiesfrontend.parse import func_for_letter from bpython._py3compat import py3 if not py3: import inspect diff --git a/bpython/test/test_curtsies_parser.py b/bpython/test/test_curtsies_parser.py new file mode 100644 index 000000000..09cae32b3 --- /dev/null +++ b/bpython/test/test_curtsies_parser.py @@ -0,0 +1,18 @@ +import unittest + +from bpython.curtsiesfrontend import parse +from curtsies.fmtfuncs import yellow, cyan, green, bold + +class TestExecArgs(unittest.TestCase): + + def test_parse(self): + self.assertEquals(parse.parse(u'\x01y\x03print\x04'), + yellow(u'print')) + + self.assertEquals(parse.parse(u'\x01y\x03print\x04\x01c\x03 \x04\x01g\x031\x04\x01c\x03 \x04\x01Y\x03+\x04\x01c\x03 \x04\x01g\x032\x04'), + yellow(u'print')+cyan(u' ')+green(u'1')+cyan(u' ')+bold(yellow(u'+'))+cyan(' ')+green(u'2')) + + def test_peal_off_string(self): + self.assertEquals(parse.peel_off_string('\x01RI\x03]\x04asdf'), + ({'bg': 'I', 'string': ']', 'fg': 'R', 'colormarker': + '\x01RI', 'bold': ''}, 'asdf')) From 3afa1db8e4527c39abaf4195bc692c90f92d70cd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 07:37:40 +0100 Subject: [PATCH 0129/1650] Expand history file path only once Signed-off-by: Sebastian Ramacher --- bpython/config.py | 2 ++ bpython/repl.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index a6f0e3569..d804a6d03 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -229,6 +229,8 @@ def get_key_no_doublebind(attr, already_used={}): for key in (struct.pastebin_key, struct.save_key): key_dispatch[key] + struct.hist_file = os.path.expanduser(struct.hist_file) + def load_theme(struct, path, colors, default_colors): theme = ConfigParser() with open(path, 'r') as f: diff --git a/bpython/repl.py b/bpython/repl.py index b767189d7..a19d7fbcc 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -890,7 +890,7 @@ def push(self, s, insert_into_history=True): def insert_into_history(self, s): if self.config.hist_length: - histfilename = os.path.expanduser(self.config.hist_file) + histfilename = self.config.hist_file oldhistory = self.rl_history.entries self.rl_history.entries = [] if os.path.exists(histfilename): From 4cd6684e786e2a677a9fa47e5b61d2071763ee03 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 07:47:57 +0100 Subject: [PATCH 0130/1650] Remove unnecessary close Signed-off-by: Sebastian Ramacher --- bpython/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index d804a6d03..274cd1f60 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -245,4 +245,3 @@ def load_theme(struct, path, colors, default_colors): for k, v in default_colors.iteritems(): if k not in colors: colors[k] = v - f.close() From 65a17cf8420c02b617c438884ef7a59834e32031 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 08:12:18 +0100 Subject: [PATCH 0131/1650] Use except Foo as e consistently Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 4 ++-- bpython/curtsies.py | 2 +- bpython/curtsiesfrontend/repl.py | 2 +- bpython/pager.py | 8 +++++--- bpython/repl.py | 10 +++++----- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 46db9d7e1..ac7075e60 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1077,7 +1077,7 @@ def push(self, s, insert_into_history=True): curses.raw(False) try: return repl.Repl.push(self, s, insert_into_history) - except SystemExit, e: + except SystemExit as e: # Avoid a traceback on e.g. quit() self.do_exit = True self.exit_value = e.args @@ -1895,7 +1895,7 @@ def main_curses(scr, args, config, interactive=True, locals_=None, exit_value = () try: bpython.args.exec_code(interpreter, args) - except SystemExit, e: + except SystemExit as e: # The documentation of code.InteractiveInterpreter.runcode claims # that it reraises SystemExit. However, I can't manage to trigger # that. To be one the safe side let's catch SystemExit here anyway. diff --git a/bpython/curtsies.py b/bpython/curtsies.py index d0282b92f..e98b2fd2b 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -58,7 +58,7 @@ def main(args=None, locals_=None, banner=None): try: interp = code.InteractiveInterpreter(locals=locals_) bpargs.exec_code(interp, exec_args) - except SystemExit, e: + except SystemExit as e: exit_value = e.args if not options.interactive: raise SystemExit(exit_value) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 7eed90c81..ee1b55227 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1351,7 +1351,7 @@ def pager(self, text): def show_source(self): try: source = self.get_source_of_current_name() - except SourceNotFound, e: + except SourceNotFound as e: self.status_bar.message(_(e)) else: if self.config.highlight_show_source: diff --git a/bpython/pager.py b/bpython/pager.py index 73cc8f5f5..b397b2827 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -54,20 +54,22 @@ def page(data, use_internal=False): data = data.encode(sys.__stdout__.encoding, 'replace') popen.stdin.write(data) popen.stdin.close() - except OSError, e: + except OSError as e: if e.errno == errno.ENOENT: # pager command not found, fall back to internal pager page_internal(data) return - except IOError, e: + except IOError as e: if e.errno != errno.EPIPE: raise while True: try: popen.wait() - except OSError, e: + except OSError as e: if e.errno != errno.EINTR: raise else: break curses.doupdate() + +# vim: sw=4 ts=4 sts=4 ai et diff --git a/bpython/repl.py b/bpython/repl.py index a19d7fbcc..8c016434d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -602,11 +602,11 @@ def get_source_of_current_name(self): if inspection.is_eval_safe_name(line): obj = self.get_object(line) return inspect.getsource(obj) - except (AttributeError, NameError), e: + except (AttributeError, NameError) as e: msg = "Cannot get source: " + str(e) - except IOError, e: + except IOError as e: msg = str(e) - except TypeError, e: + except TypeError as e: if "built-in" in str(e): msg = "Cannot access source of %r" % (obj, ) else: @@ -840,7 +840,7 @@ def do_pastebin_helper(self, s): helper.stdin.write(s.encode(getpreferredencoding())) output = helper.communicate()[0].decode(getpreferredencoding()) paste_url = output.split()[0] - except OSError, e: + except OSError as e: if e.errno == errno.ENOENT: self.interact.notify(_('Upload failed: ' 'Helper program not found.')) @@ -898,7 +898,7 @@ def insert_into_history(self, s): self.rl_history.append(s) try: self.rl_history.save(histfilename, getpreferredencoding(), self.config.hist_length) - except EnvironmentError, err: + except EnvironmentError as err: self.interact.notify("Error occured while writing to file %s (%s) " % (histfilename, err.strerror)) self.rl_history.entries = oldhistory self.rl_history.append(s) From 99a889b47a48f0e9dbf9d74f81cf185540c8caff Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 08:12:36 +0100 Subject: [PATCH 0132/1650] Remove unnecessary import Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index ac7075e60..f916aba64 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1957,7 +1957,6 @@ def main(args=None, locals_=None, banner=None): return repl.extract_exit_value(exit_value) if __name__ == '__main__': - from bpython.cli import main sys.exit(main()) # vim: sw=4 ts=4 sts=4 ai et From 9c43cdd8b6526d4b98ecf4fcf426167ced7faed4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 08:16:53 +0100 Subject: [PATCH 0133/1650] Tests are now in bpython.tests Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/parse.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 37f80b33e..5c089cc2e 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -63,11 +63,6 @@ def fs_from_match(d): return fmtstr(d['string'], **atts) def peel_off_string(s): - r""" - >>> r = peel_off_string('\x01RI\x03]\x04asdf') - >>> r == ({'bg': 'I', 'string': ']', 'fg': 'R', 'colormarker': '\x01RI', 'bold': ''}, 'asdf') - True - """ p = r"""(?P\x01 (?P[krgybmcwdKRGYBMCWD]?) (?P[krgybmcwdKRGYBMCWDI]?)?) From d9274182c9eba0925b8d6480f6b9e5b61c3af148 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 08:17:11 +0100 Subject: [PATCH 0134/1650] Fix a pyflakes warning --- bpython/curtsiesfrontend/interpreter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index a980079c7..850674a85 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,7 +1,9 @@ import code import traceback import sys -from pygments.token import * +from pygments.token import Generic, Token, Keyword, Name, Comment, String +from pygments.token import Error, Literal, Number, Operator, Punctuation +from pygments.token import Whitespace from pygments.formatter import Formatter from bpython.curtsiesfrontend.parse import parse from codeop import CommandCompiler From 9fa5f8dbfc701e4f070f699ef990f94637b6a254 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 08:25:20 +0100 Subject: [PATCH 0135/1650] Use with Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 8c016434d..427ef363d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -761,9 +761,8 @@ def write2file(self): s = self.formatforfile(self.getstdout()) try: - f = open(fn, mode) - f.write(s) - f.close() + with open(fn, mode) as f: + f.write(s) except IOError: self.interact.notify("Disk write error for file '%s'." % (fn, )) else: From 6d7fa191396bca1080f6759836f8471a089596cf Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 08:32:05 +0100 Subject: [PATCH 0136/1650] Verify completion mode Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 2 ++ bpython/config.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 914eef150..b3e6a95af 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -48,6 +48,8 @@ SUBSTRING = 'substring' FUZZY = 'fuzzy' +ALL_MODES = (SIMPLE, SUBSTRING, FUZZY) + MAGIC_METHODS = ["__%s__" % s for s in [ "init", "repr", "str", "lt", "le", "eq", "ne", "gt", "ge", "cmp", "hash", "nonzero", "unicode", "getattr", "setattr", "get", "set","call", "len", diff --git a/bpython/config.py b/bpython/config.py index 274cd1f60..0b0abcb16 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -6,6 +6,8 @@ from bpython.keys import cli_key_dispatch as key_dispatch from bpython.autocomplete import SIMPLE as default_completion +import bpython.autocomplete + class Struct(object): """Simple class for instantiating objects we can add arbitrary attributes to and use for various arbitrary things.""" @@ -229,8 +231,13 @@ def get_key_no_doublebind(attr, already_used={}): for key in (struct.pastebin_key, struct.save_key): key_dispatch[key] + # expand path of history file struct.hist_file = os.path.expanduser(struct.hist_file) + # verify completion mode + if struct.autocomplete_mode not in bpython.autocomplete.ALL_MODES: + struct.autocomplete_mode = default_completion + def load_theme(struct, path, colors, default_colors): theme = ConfigParser() with open(path, 'r') as f: From e837c925495558d4d91ce885491cbc728ed475ff Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 08:32:18 +0100 Subject: [PATCH 0137/1650] autocomplete_mode is always set Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 427ef363d..d8e5d9bba 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -649,7 +649,7 @@ def complete(self, tab=False): self.interp.locals, self.argspec, '\n'.join(self.buffer + [self.current_line]), - self.config.autocomplete_mode if hasattr(self.config, 'autocomplete_mode') else autocomplete.SIMPLE, + self.config.autocomplete_mode, self.config.complete_magic_methods) #TODO implement completer.shown_before_tab == False (filenames shouldn't fill screen) From bfa13270656dc328d185a856b85a7314b4b12f37 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 09:10:13 +0100 Subject: [PATCH 0138/1650] Compile regular expressions only once Signed-off-by: Sebastian Ramacher --- bpython/line.py | 52 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index ee7114670..c3d53b3c6 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -5,10 +5,30 @@ import re +current_word_re = re.compile(r'[\w_][\w0-9._]*[(]?') +current_dict_key_re = re.compile(r'''[\w_][\w0-9._]*\[([\w0-9._(), '"]*)''') +current_dict_re = re.compile(r'''([\w_][\w0-9._]*)\[([\w0-9._(), '"]*)''') +current_string_re = re.compile( + '''(?P(?:""")|"|(?:''\')|')(?:((?P.+?)(?P=open))|(?P.+))''') +current_object_re = re.compile(r'([\w_][\w0-9_]*)[.]') +current_object_attribute_re = re.compile(r'([\w_][\w0-9_]*)[.]?') +current_from_import_from_re = re.compile(r'from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*') +current_from_import_import_re_1 = re.compile(r'from\s([\w0-9_.]*)\s+import') +current_from_import_import_re_2 = re.compile(r'([\w0-9_]+)') +current_from_import_import_re_3 = re.compile(r'[,][ ]([\w0-9_]*)') +current_import_re_1 = re.compile(r'import') +current_import_re_2 = re.compile(r'([\w0-9_.]+)') +current_import_re_3 = re.compile(r'[,][ ]([\w0-9_.]*)') +current_method_definition_name_re = re.compile("def\s+([a-zA-Z_][\w]*)") +current_single_word_re = re.compile(r"(?= cursor_offset: return (m.start(1), m.end(1), m.group(1)) @@ -31,7 +51,7 @@ def current_dict_key(cursor_offset, line): def current_dict(cursor_offset, line): """If in dictionary completion, return the dict that should be used""" - matches = list(re.finditer(r'''([\w_][\w0-9._]*)\[([\w0-9._(), '"]*)''', line)) + matches = list(current_dict_re.finditer(line)) for m in matches: if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: return (m.start(1), m.end(1), m.group(1)) @@ -42,7 +62,7 @@ def current_string(cursor_offset, line): Weaker than bpython.Repl's current_string, because that checks that a string is a string based on previous lines in the buffer""" - for m in re.finditer('''(?P(?:""")|"|(?:''\')|')(?:((?P.+?)(?P=open))|(?P.+))''', line): + for m in current_string_re.finditer(line): i = 3 if m.group(3) else 4 if m.start(i) <= cursor_offset and m.end(i) >= cursor_offset: return m.start(i), m.end(i), m.group(i) @@ -53,7 +73,7 @@ def current_object(cursor_offset, line): match = current_word(cursor_offset, line) if match is None: return None start, end, word = match - matches = list(re.finditer(r'([\w_][\w0-9_]*)[.]', word)) + matches = list(current_object_re.finditer(word)) s = '' for m in matches: if m.end(1) + start < cursor_offset: @@ -69,7 +89,7 @@ def current_object_attribute(cursor_offset, line): match = current_word(cursor_offset, line) if match is None: return None start, end, word = match - matches = list(re.finditer(r'([\w_][\w0-9_]*)[.]?', word)) + matches = list(current_object_attribute_re.finditer(word)) for m in matches[1:]: if m.start(1) + start <= cursor_offset and m.end(1) + start >= cursor_offset: return m.start(1) + start, m.end(1) + start, m.group(1) @@ -85,7 +105,7 @@ def current_from_import_from(cursor_offset, line): tokens = line.split() if not ('from' in tokens or 'import' in tokens): return None - matches = list(re.finditer(r'from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*', line)) + matches = list(current_from_import_from_re.finditer(line)) for m in matches: if ((m.start(1) < cursor_offset and m.end(1) >= cursor_offset) or (m.start(2) < cursor_offset and m.end(2) >= cursor_offset)): @@ -97,13 +117,13 @@ def current_from_import_import(cursor_offset, line): returns None if cursor not in or just after one of these words """ - baseline = re.search(r'from\s([\w0-9_.]*)\s+import', line) + baseline = current_from_import_import_re_1.search(line) if baseline is None: return None - match1 = re.search(r'([\w0-9_]+)', line[baseline.end():]) + match1 = current_from_import_import_re_2.search(line[baseline.end():]) if match1 is None: return None - matches = list(re.finditer(r'[,][ ]([\w0-9_]*)', line[baseline.end():])) + matches = list(current_from_import_import_re_3.finditer(line[baseline.end():])) for m in [match1] + matches: start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) @@ -113,13 +133,13 @@ def current_from_import_import(cursor_offset, line): def current_import(cursor_offset, line): #TODO allow for multiple as's - baseline = re.search(r'import', line) + baseline = current_import_re_1.search(line) if baseline is None: return None - match1 = re.search(r'([\w0-9_.]+)', line[baseline.end():]) + match1 = current_import_re_2.search(line[baseline.end():]) if match1 is None: return None - matches = list(re.finditer(r'[,][ ]([\w0-9_.]*)', line[baseline.end():])) + matches = list(current_import_re_3.finditer(line[baseline.end():])) for m in [match1] + matches: start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) @@ -128,7 +148,7 @@ def current_import(cursor_offset, line): def current_method_definition_name(cursor_offset, line): """The name of a method being defined""" - matches = re.finditer("def\s+([a-zA-Z_][\w]*)", line) + matches = current_method_definition_name_re.finditer(line) for m in matches: if (m.start(1) <= cursor_offset and m.end(1) >= cursor_offset): return m.start(1), m.end(1), m.group(1) @@ -136,7 +156,7 @@ def current_method_definition_name(cursor_offset, line): def current_single_word(cursor_offset, line): """the un-dotted word just before or under the cursor""" - matches = re.finditer(r"(?= cursor_offset): return m.start(1), m.end(1), m.group(1) @@ -152,7 +172,7 @@ def current_dotted_attribute(cursor_offset, line): def current_string_literal_attr(cursor_offset, line): """The attribute following a string literal""" - matches = re.finditer("('''" + r'''|"""|'|")((?:(?=([^"'\\]+|\\.|(?!\1)["']))\3)*)\1[.]([a-zA-Z_]?[\w]*)''', line) + matches = current_string_literal_attr_re.finditer(line) for m in matches: if (m.start(4) <= cursor_offset and m.end(4) >= cursor_offset): return m.start(4), m.end(4), m.group(4) From 8afe3a3915cef2c20657e3307502cb50e71f9ab6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 09:12:40 +0100 Subject: [PATCH 0139/1650] Do not create lists if not neede --- bpython/line.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index c3d53b3c6..fa460e5a5 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -28,7 +28,7 @@ def current_word(cursor_offset, line): """the object.attribute.attribute just before or under the cursor""" pos = cursor_offset - matches = list(current_word_re.finditer(line)) + matches = current_word_re.finditer(line) start = pos end = pos word = None @@ -43,7 +43,7 @@ def current_word(cursor_offset, line): def current_dict_key(cursor_offset, line): """If in dictionary completion, return the current key""" - matches = list(current_dict_key_re.finditer(line)) + matches = current_dict_key_re.finditer(line) for m in matches: if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: return (m.start(1), m.end(1), m.group(1)) @@ -51,7 +51,7 @@ def current_dict_key(cursor_offset, line): def current_dict(cursor_offset, line): """If in dictionary completion, return the dict that should be used""" - matches = list(current_dict_re.finditer(line)) + matches = current_dict_re.finditer(line) for m in matches: if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: return (m.start(1), m.end(1), m.group(1)) @@ -73,7 +73,7 @@ def current_object(cursor_offset, line): match = current_word(cursor_offset, line) if match is None: return None start, end, word = match - matches = list(current_object_re.finditer(word)) + matches = current_object_re.finditer(word) s = '' for m in matches: if m.end(1) + start < cursor_offset: @@ -105,7 +105,7 @@ def current_from_import_from(cursor_offset, line): tokens = line.split() if not ('from' in tokens or 'import' in tokens): return None - matches = list(current_from_import_from_re.finditer(line)) + matches = current_from_import_from_re.finditer(line) for m in matches: if ((m.start(1) < cursor_offset and m.end(1) >= cursor_offset) or (m.start(2) < cursor_offset and m.end(2) >= cursor_offset)): From d6bb34781a8055e3e76d475b4b6d5bebb40ae262 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 09:13:03 +0100 Subject: [PATCH 0140/1650] Use except Exception as e consistently Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index f916aba64..c06602950 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -969,7 +969,7 @@ def p_key(self, key): elif key in key_dispatch[config.show_source_key]: try: source = self.get_source_of_current_name() - except repl.SourceNotFound, e: + except repl.SourceNotFound as e: self.statusbar.message(_(e)) else: if config.highlight_show_source: From da2a126fa141348a4767112d563bd31400735930 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 09:26:02 +0100 Subject: [PATCH 0141/1650] Remove remaining lists Signed-off-by: Sebastian Ramacher --- bpython/line.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index fa460e5a5..21c28a100 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -4,6 +4,8 @@ and return None, or a tuple of the start index, end index, and the word""" import re +from itertools import chain + current_word_re = re.compile(r'[\w_][\w0-9._]*[(]?') current_dict_key_re = re.compile(r'''[\w_][\w0-9._]*\[([\w0-9._(), '"]*)''') @@ -89,8 +91,9 @@ def current_object_attribute(cursor_offset, line): match = current_word(cursor_offset, line) if match is None: return None start, end, word = match - matches = list(current_object_attribute_re.finditer(word)) - for m in matches[1:]: + matches = current_object_attribute_re.finditer(word) + matches.next() + for m in matches: if m.start(1) + start <= cursor_offset and m.end(1) + start >= cursor_offset: return m.start(1) + start, m.end(1) + start, m.group(1) return None @@ -123,8 +126,8 @@ def current_from_import_import(cursor_offset, line): match1 = current_from_import_import_re_2.search(line[baseline.end():]) if match1 is None: return None - matches = list(current_from_import_import_re_3.finditer(line[baseline.end():])) - for m in [match1] + matches: + matches = current_from_import_import_re_3.finditer(line[baseline.end():]) + for m in chain((match1, ), matches): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: @@ -139,8 +142,8 @@ def current_import(cursor_offset, line): match1 = current_import_re_2.search(line[baseline.end():]) if match1 is None: return None - matches = list(current_import_re_3.finditer(line[baseline.end():])) - for m in [match1] + matches: + matches = current_import_re_3.finditer(line[baseline.end():]) + for m in chain((match1, ), matches): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: From d21a9907afd59e9d5ce31c3afb2d4178126c49d0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 09:26:20 +0100 Subject: [PATCH 0142/1650] Kepp regular expressions close to their use Signed-off-by: Sebastian Ramacher --- bpython/line.py | 59 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 21c28a100..883c6fc96 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -8,24 +8,6 @@ current_word_re = re.compile(r'[\w_][\w0-9._]*[(]?') -current_dict_key_re = re.compile(r'''[\w_][\w0-9._]*\[([\w0-9._(), '"]*)''') -current_dict_re = re.compile(r'''([\w_][\w0-9._]*)\[([\w0-9._(), '"]*)''') -current_string_re = re.compile( - '''(?P(?:""")|"|(?:''\')|')(?:((?P.+?)(?P=open))|(?P.+))''') -current_object_re = re.compile(r'([\w_][\w0-9_]*)[.]') -current_object_attribute_re = re.compile(r'([\w_][\w0-9_]*)[.]?') -current_from_import_from_re = re.compile(r'from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*') -current_from_import_import_re_1 = re.compile(r'from\s([\w0-9_.]*)\s+import') -current_from_import_import_re_2 = re.compile(r'([\w0-9_]+)') -current_from_import_import_re_3 = re.compile(r'[,][ ]([\w0-9_]*)') -current_import_re_1 = re.compile(r'import') -current_import_re_2 = re.compile(r'([\w0-9_.]+)') -current_import_re_3 = re.compile(r'[,][ ]([\w0-9_.]*)') -current_method_definition_name_re = re.compile("def\s+([a-zA-Z_][\w]*)") -current_single_word_re = re.compile(r"(?(?:""")|"|(?:''\')|')(?:((?P.+?)(?P=open))|(?P.+))''') + def current_string(cursor_offset, line): """If inside a string of nonzero length, return the string (excluding quotes) @@ -70,6 +62,9 @@ def current_string(cursor_offset, line): return m.start(i), m.end(i), m.group(i) return None + +current_object_re = re.compile(r'([\w_][\w0-9_]*)[.]') + def current_object(cursor_offset, line): """If in attribute completion, the object on which attribute should be looked up""" match = current_word(cursor_offset, line) @@ -86,6 +81,9 @@ def current_object(cursor_offset, line): return None return start, start+len(s), s + +current_object_attribute_re = re.compile(r'([\w_][\w0-9_]*)[.]?') + def current_object_attribute(cursor_offset, line): """If in attribute completion, the attribute being completed""" match = current_word(cursor_offset, line) @@ -98,6 +96,9 @@ def current_object_attribute(cursor_offset, line): return m.start(1) + start, m.end(1) + start, m.group(1) return None + +current_from_import_from_re = re.compile(r'from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*') + def current_from_import_from(cursor_offset, line): """If in from import completion, the word after from @@ -115,6 +116,11 @@ def current_from_import_from(cursor_offset, line): return m.start(1), m.end(1), m.group(1) return None + +current_from_import_import_re_1 = re.compile(r'from\s([\w0-9_.]*)\s+import') +current_from_import_import_re_2 = re.compile(r'([\w0-9_]+)') +current_from_import_import_re_3 = re.compile(r'[,][ ]([\w0-9_]*)') + def current_from_import_import(cursor_offset, line): """If in from import completion, the word after import being completed @@ -134,6 +140,11 @@ def current_from_import_import(cursor_offset, line): return start, end, m.group(1) return None + +current_import_re_1 = re.compile(r'import') +current_import_re_2 = re.compile(r'([\w0-9_.]+)') +current_import_re_3 = re.compile(r'[,][ ]([\w0-9_.]*)') + def current_import(cursor_offset, line): #TODO allow for multiple as's baseline = current_import_re_1.search(line) @@ -149,6 +160,9 @@ def current_import(cursor_offset, line): if start < cursor_offset and end >= cursor_offset: return start, end, m.group(1) + +current_method_definition_name_re = re.compile("def\s+([a-zA-Z_][\w]*)") + def current_method_definition_name(cursor_offset, line): """The name of a method being defined""" matches = current_method_definition_name_re.finditer(line) @@ -157,6 +171,9 @@ def current_method_definition_name(cursor_offset, line): return m.start(1), m.end(1), m.group(1) return None + +current_single_word_re = re.compile(r"(? Date: Fri, 9 Jan 2015 09:32:19 +0100 Subject: [PATCH 0143/1650] Fix curtsies version constraints Signed-off-by: Sebastian Ramacher --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 874278737..ea7912977 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ install: # core dependencies - pip install pygments requests # curtsies specific dependencies - - pip install 'curtsies<0.2.0' greenlet + - pip install 'curtsies >=0.1.15,<0.2.0' greenlet # translation specific dependencies - pip install babel # documentation specific dependencies From e4a5f66dd389771649f0451d22f1ad9f5f30538e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 10:42:12 +0100 Subject: [PATCH 0144/1650] Remove pre 2.6 code Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index b3e6a95af..7bcb28e85 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -24,6 +24,7 @@ from __future__ import with_statement import __builtin__ import __main__ +import abc import rlcompleter import line as lineparts import re @@ -33,16 +34,6 @@ from bpython import importcompletion from bpython._py3compat import py3 -# Needed for special handling of __abstractmethods__ -# abc only exists since 2.6, so check both that it exists and that it's -# the one we're expecting -try: - import abc - abc.ABCMeta - has_abc = True -except (ImportError, AttributeError): - has_abc = False - # Autocomplete modes SIMPLE = 'simple' SUBSTRING = 'substring' @@ -338,7 +329,7 @@ def attr_lookup(obj, expr, attr, autocomplete_mode): if hasattr(obj, '__class__'): words.append('__class__') words = words + rlcompleter.get_class_members(obj.__class__) - if has_abc and not isinstance(obj.__class__, abc.ABCMeta): + if not isinstance(obj.__class__, abc.ABCMeta): try: words.remove('__abstractmethods__') except ValueError: From c003bab6f19c77225ad173da8203b367ac444f74 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 10:48:47 +0100 Subject: [PATCH 0145/1650] Replace mercurial with git Signed-off-by: Sebastian Ramacher --- bpython/__init__.py | 2 +- doc/sphinx/source/conf.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/__init__.py b/bpython/__init__.py index b93f04297..1cce7cc6f 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -22,7 +22,7 @@ import os.path -__version__ = 'mercurial' +__version__ = 'git' package_dir = os.path.abspath(os.path.dirname(__file__)) diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py index 805ef7e8f..348a07331 100644 --- a/doc/sphinx/source/conf.py +++ b/doc/sphinx/source/conf.py @@ -45,9 +45,9 @@ # built documents. # # The short X.Y version. -version = 'mercurial' +version = 'git' # The full version, including alpha/beta/rc tags. -release = 'mercurial' +release = 'git' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From eb499b87bdbaf02f9f4dee5692cd478b6b4ce4e3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 10:54:33 +0100 Subject: [PATCH 0146/1650] Add links to documentation (fixes #383) Signed-off-by: Sebastian Ramacher --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index 951f55c1b..b53963d5b 100644 --- a/README.rst +++ b/README.rst @@ -159,4 +159,11 @@ To correct this I have provided my windows.theme file. This curses implementation has 16 colors (dark and light versions of the colours) +See also +======== + +Documentation + http://docs.bpython-interpreter.org/ +Developer documentation + http://docs.bpython-interpreter.org/contributing.html From ad584cb77634932d8632f2e7ea54ce0e06b510f2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 11:05:30 +0100 Subject: [PATCH 0147/1650] Store compiled regex Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 7bcb28e85..8243ec2ab 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -297,6 +297,8 @@ def safe_eval(expr, namespace): raise EvaluationError +attr_matches_re = re.compile(r"(\w+(\.\w+)*)\.(\w*)") + def attr_matches(text, namespace, autocomplete_mode): """Taken from rlcompleter.py and bent to my will. """ @@ -304,7 +306,7 @@ def attr_matches(text, namespace, autocomplete_mode): # Gna, Py 2.6's rlcompleter searches for __call__ inside the # instance instead of the type, so we monkeypatch to prevent # side-effects (__getattr__/__getattribute__) - m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) + m = attr_matches_re.match(text) if not m: return [] From b07e149d29205cbbe04739cb89a17930c6537ef7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 13:49:15 +0100 Subject: [PATCH 0148/1650] Move tests to bpython.tests Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/coderunner.py | 25 ---------------- bpython/test/test_curtsies_coderunner.py | 38 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 bpython/test/test_curtsies_coderunner.py diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index e10897ee2..99314d08e 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -186,28 +186,3 @@ def flush(self): def isatty(self): return True -def test_simple(): - orig_stdout = sys.stdout - orig_stderr = sys.stderr - c = CodeRunner(request_refresh=lambda: orig_stdout.flush() or orig_stderr.flush()) - stdout = FakeOutput(c, orig_stdout.write) - sys.stdout = stdout - c.load_code('1 + 1') - c.run_code() - c.run_code() - c.run_code() - -def test_exception(): - orig_stdout = sys.stdout - orig_stderr = sys.stderr - c = CodeRunner(request_refresh=lambda: orig_stdout.flush() or orig_stderr.flush()) - def ctrlc(): - raise KeyboardInterrupt() - stdout = FakeOutput(c, lambda x: ctrlc()) - sys.stdout = stdout - c.load_code('1 + 1') - c.run_code() - -if __name__ == '__main__': - test_simple() - diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py new file mode 100644 index 000000000..80021503d --- /dev/null +++ b/bpython/test/test_curtsies_coderunner.py @@ -0,0 +1,38 @@ +import unittest +import sys + +from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput + +class TestCodeRunner(unittest.TestCase): + + def setUp(self): + self.orig_stdout = sys.stdout + self.orig_stderr = sys.stderr + + def tearDown(self): + sys.stdout = self.orig_stdout + sys.stderr = self.orig_stderr + + def test_simple(self): + c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or self.orig_stderr.flush()) + stdout = FakeOutput(c, lambda *args, **kwargs: None) + stderr = FakeOutput(c, lambda *args, **kwargs: None) + sys.stdout = stdout + sys.stdout = stderr + c.load_code('1 + 1') + c.run_code() + c.run_code() + c.run_code() + + def test_exception(self): + orig_stdout = sys.stdout + orig_stderr = sys.stderr + c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or self.orig_stderr.flush()) + def ctrlc(): + raise KeyboardInterrupt() + stdout = FakeOutput(c, lambda x: ctrlc()) + stderr = FakeOutput(c, lambda *args, **kwargs: None) + sys.stdout = stdout + sys.stderr = stderr + c.load_code('1 + 1') + c.run_code() From d28b3ab15dd61c9f1f9dc5af9ed7cf7105572a40 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 13:49:29 +0100 Subject: [PATCH 0149/1650] Add newlines Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index ee1b55227..b53d39367 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1214,6 +1214,7 @@ def __repr__(self): def _get_current_line(self): return self._current_line + def _set_current_line(self, line, update_completion=True, reset_rl_history=True, clear_special_mode=True): if self._current_line == line: return @@ -1227,8 +1228,10 @@ def _set_current_line(self, line, update_completion=True, reset_rl_history=True, self.unhighlight_paren() current_line = property(_get_current_line, _set_current_line, None, "The current line") + def _get_cursor_offset(self): return self._cursor_offset + def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=False, clear_special_mode=True): if self._cursor_offset == offset: return @@ -1241,8 +1244,10 @@ def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=Fa self._cursor_offset = offset self.update_completion() self.unhighlight_paren() + cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, "The current cursor offset from the front of the line") + def echo(self, msg, redraw=True): """ Notification that redrawing the current line is necessary (we don't @@ -1252,10 +1257,12 @@ def echo(self, msg, redraw=True): It's not supposed to update the screen if it's reevaluating the code (as it does with undo).""" logger.debug("echo called with %r" % msg) + @property def cpos(self): "many WATs were had - it's the pos from the end of the line back""" return len(self.current_line) - self.cursor_offset + def reprint_line(self, lineno, tokens): logger.debug("calling reprint line with %r %r", lineno, tokens) if self.config.syntax: From 347c7c88c475375b839a6dda274589a02eca1c56 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 15:24:51 +0100 Subject: [PATCH 0150/1650] Switch default binary to curtsies Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/man-bpython.rst | 4 ++-- setup.py | 26 ++++++++++++++------------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/doc/sphinx/source/man-bpython.rst b/doc/sphinx/source/man-bpython.rst index 2242633a7..6a0fa8398 100644 --- a/doc/sphinx/source/man-bpython.rst +++ b/doc/sphinx/source/man-bpython.rst @@ -8,9 +8,9 @@ Synopsis **bpython** [*options*] [*file* [*args*]] -**bpython-urwid** [*options*] [*file* [*args*]] +**bpython-curses** [*options*] [*file* [*args*]] -**bpython-curtsies** [*options*] [*file* [*args*]] +**bpython-urwid** [*options*] [*file* [*args*]] Description diff --git a/setup.py b/setup.py index 1818471ae..4f2898f59 100755 --- a/setup.py +++ b/setup.py @@ -125,30 +125,32 @@ def initialize_options(self): install_requires = [ 'pygments', - 'requests' + 'requests', + 'curtsies >=0.1.15, <0.2.0', + 'greenlet' ] extras_require = { - 'urwid' : ['urwid'] + 'urwid' : ['urwid'], + 'watch' : ['watchdog'] } -packages = ['bpython', 'bpython.test', 'bpython.translations', 'bpdb'] +packages = [ + 'bpython', + 'bpython.curtsiesfrontend', + 'bpython.test', + 'bpython.translations', + 'bpdb' +] entry_points = { 'console_scripts': [ - 'bpython = bpython.cli:main', + 'bpython = bpython.curtsies:main', + 'bpython-curses = bpython.cli:main', 'bpython-urwid = bpython.urwid:main [urwid]' ] } -if sys.version_info[:2] >= (2, 6): - # curtsies only supports 2.6 and onwards - extras_require['curtsies'] = ['curtsies >=0.1.15, <0.2.0', 'greenlet'] - extras_require['watch'] = ['watchdog'] - packages.append("bpython.curtsiesfrontend") - entry_points['console_scripts'].append( - 'bpython-curtsies = bpython.curtsies:main [curtsies]') - if sys.version_info[0] == 2 and sys.platform == "darwin": # need PyOpenSSL for SNI support (only 2.X and on Darwin) # list of packages taken from From 25838bfad2e68c39cb36ae56d05b1ea5991472e0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 16:33:02 +0100 Subject: [PATCH 0151/1650] Automatic versioning (fixes #333) Signed-off-by: Sebastian Ramacher --- .gitignore | 1 + bpython/__init__.py | 4 +++- setup.py | 30 +++++++++++++++++++++++++++--- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 62cb91fb7..f5a04a965 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ env .DS_Store .idea/ doc/sphinx/build/* +bpython/_version.py diff --git a/bpython/__init__.py b/bpython/__init__.py index 1cce7cc6f..19bb7edbb 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -21,8 +21,10 @@ # THE SOFTWARE. import os.path +import bpython._version -__version__ = 'git' + +__version__ = bpython._version.__version__ package_dir = os.path.abspath(os.path.dirname(__file__)) diff --git a/setup.py b/setup.py index 4f2898f59..b20fb4c3d 100755 --- a/setup.py +++ b/setup.py @@ -5,12 +5,13 @@ import os import platform import sys +import subprocess from distutils.command.build import build from setuptools import setup from setuptools.command.install import install as _install -from bpython import __version__, package_dir +from bpython import package_dir try: from babel.messages.frontend import compile_catalog as _compile_catalog @@ -33,6 +34,25 @@ except ImportError: using_sphinx = False + +# version handling +version_file = 'bpython/_version.py' + +try: + # get version from git describe + version = subprocess.check_output(['git', 'describe', '--tags']).rstrip() +except OSError: + try: + # get version from existing version file + with open(version_file) as vf: + version = vf.read().strip().split('=')[-1].replace('\'', '') + except IOError: + version = 'unknown' + +with open(version_file, 'w') as vf: + vf.write('# Auto-generated file, do not edit!\n') + vf.write('__version__=\'%s\'\n' % (version, )) + class install(_install): """Force install to run build target.""" @@ -40,7 +60,11 @@ def run(self): self.run_command('build') _install.run(self) -cmdclass = dict(build=build, install=install) +cmdclass = { + 'build': build, + 'install': install +} + translations_dir = os.path.join(package_dir, 'translations') # localization options @@ -172,7 +196,7 @@ def initialize_options(self): setup( name="bpython", - version = __version__, + version = version, author = "Bob Farrell, Andreas Stuehrk et al.", author_email = "robertanthonyfarrell@gmail.com", description = "Fancy Interface to the Python Interpreter", From 843de503e4cd55c2fab7593d121159e7ea08ec40 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 16:41:58 +0100 Subject: [PATCH 0152/1650] Use nicer version numbers Signed-off-by: Sebastian Ramacher --- setup.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/setup.py b/setup.py index b20fb4c3d..e627040e5 100755 --- a/setup.py +++ b/setup.py @@ -41,6 +41,14 @@ try: # get version from git describe version = subprocess.check_output(['git', 'describe', '--tags']).rstrip() + version_split = version.split('-') + if len(version_split) == 4: + # format: version-release-commits-hash + version = '-'.join((version_split[0], version_split[2])) + elif len(version_split) == 2: + # format: version-release + version = version_split[0] + except OSError: try: # get version from existing version file From 717206cfe21ee89fa1d2afdf3f756621db2d617f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 16:44:16 +0100 Subject: [PATCH 0153/1650] There is no tex documentaiton Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/conf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py index 348a07331..3a76bda0c 100644 --- a/doc/sphinx/source/conf.py +++ b/doc/sphinx/source/conf.py @@ -171,10 +171,10 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'bpython.tex', u'bpython Documentation', - u'Robert Farrell', 'manual'), -] +#latex_documents = [ +# ('index', 'bpython.tex', u'bpython Documentation', +# u'Robert Farrell', 'manual'), +#] # The name of an image file (relative to this directory) to place at the top of # the title page. From 1094abe6f4c3dcaafb376f1a5d964189af7bc589 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 16:47:25 +0100 Subject: [PATCH 0154/1650] Use auto-generated version number for sphinx documentation Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/conf.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py index 3a76bda0c..85fb27ee9 100644 --- a/doc/sphinx/source/conf.py +++ b/doc/sphinx/source/conf.py @@ -11,7 +11,7 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -45,9 +45,15 @@ # built documents. # # The short X.Y version. -version = 'git' + +version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), + '../../../bpython/_version.py') + +with open(version_file) as vf: + version = vf.read().strip().split('=')[-1].replace('\'', '') + # The full version, including alpha/beta/rc tags. -release = 'git' +release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 74ce9642236dd4be23a533ec123cef7d71eaca3b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 16:55:16 +0100 Subject: [PATCH 0155/1650] Do not fail if bpython._version does not exist Signed-off-by: Sebastian Ramacher --- bpython/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bpython/__init__.py b/bpython/__init__.py index 19bb7edbb..505ef61e2 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -21,10 +21,13 @@ # THE SOFTWARE. import os.path -import bpython._version +try: + from bpython._version import __version__ as version +except ImportError: + version = 'unkown' -__version__ = bpython._version.__version__ +__version__ = version package_dir = os.path.abspath(os.path.dirname(__file__)) From 8f628bbf6811a8b5f8cb76dcf93c94120d92338f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 17:11:56 +0100 Subject: [PATCH 0156/1650] Catch correct exception Signed-off-by: Sebastian Ramacher --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index e627040e5..c02fe1874 100755 --- a/setup.py +++ b/setup.py @@ -48,8 +48,7 @@ elif len(version_split) == 2: # format: version-release version = version_split[0] - -except OSError: +except subprocess.CalledProcessError: try: # get version from existing version file with open(version_file) as vf: From f66393c88427179d459674aa63d4e8701651ea6d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 17:16:10 +0100 Subject: [PATCH 0157/1650] Import package_dir as late as possible Signed-off-by: Sebastian Ramacher --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c02fe1874..82024bdc0 100755 --- a/setup.py +++ b/setup.py @@ -11,8 +11,6 @@ from setuptools import setup from setuptools.command.install import install as _install -from bpython import package_dir - try: from babel.messages.frontend import compile_catalog as _compile_catalog from babel.messages.frontend import extract_messages as _extract_messages @@ -72,6 +70,7 @@ def run(self): 'install': install } +from bpython import package_dir translations_dir = os.path.join(package_dir, 'translations') # localization options From 240c8dd2ffa0e9b2a6450c89aa1e23b5bf0ab5f5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 17:49:13 +0100 Subject: [PATCH 0158/1650] Fix 2.6 incompatibility Signed-off-by: Sebastian Ramacher --- setup.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 82024bdc0..930a6ab92 100755 --- a/setup.py +++ b/setup.py @@ -35,24 +35,32 @@ # version handling version_file = 'bpython/_version.py' +version = 'unkown' try: # get version from git describe - version = subprocess.check_output(['git', 'describe', '--tags']).rstrip() - version_split = version.split('-') - if len(version_split) == 4: - # format: version-release-commits-hash - version = '-'.join((version_split[0], version_split[2])) - elif len(version_split) == 2: - # format: version-release - version = version_split[0] -except subprocess.CalledProcessError: + proc = subprocess.Popen(['git', 'describe', '--tags'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout = proc.communicate()[0].rstrip() + + if proc.returncode == 0: + version_split = stdout.split('-') + if len(version_split) == 4: + # format: version-release-commits-hash + version = '-'.join((version_split[0], version_split[2])) + elif len(version_split) == 2: + # format: version-release + version = version_split[0] +except OSError: + pass + +if version == 'unknown': try: # get version from existing version file with open(version_file) as vf: version = vf.read().strip().split('=')[-1].replace('\'', '') except IOError: - version = 'unknown' + pass with open(version_file, 'w') as vf: vf.write('# Auto-generated file, do not edit!\n') From e34e756af1b95464f1c0efdcffe00f1cdbb985d5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 18:02:50 +0100 Subject: [PATCH 0159/1650] Update dependencies Signed-off-by: Sebastian Ramacher --- README.rst | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index b53963d5b..7fc8fc258 100644 --- a/README.rst +++ b/README.rst @@ -12,17 +12,10 @@ Dependencies * Sphinx != 1.1.2 (for the documentation only) (apt-get install python-sphinx) * mock (for the testsuite only) * babel (optional, for internationalization) - -bpython-curtsies ----------------- -``bpython-curtsies`` requires the following additional packages: - -* curtsies >= 0.1.0 +* requests +* curtsies >= 0.1.15,<0.2.0 * greenlet - -and optionally (for monitoring imported modules for changes) - -* watchdog +* watchdog (optional, (for monitoring imported modules for changes) bpython-urwid ------------- From 958fce83314dfdae6507a1d3f0a9337b3f58fbad Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 18:06:57 +0100 Subject: [PATCH 0160/1650] Update Python version numbers Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/releases.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst index 06d20a624..8443628d3 100644 --- a/doc/sphinx/source/releases.rst +++ b/doc/sphinx/source/releases.rst @@ -34,7 +34,7 @@ A checklist to perform some manual tests before a release: Check that all of the following work before a release: -* Runs under Python 2.5, 2.6 and 3.1 (after 2to3). +* Runs under Python 2.6, 2.7, 3.3, and 3.4 (after 2to3). * Save * Rewind * Pastebin From 0c0841476d0c2590aa65f51991d04ddec190decf Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 9 Jan 2015 13:28:19 -0500 Subject: [PATCH 0161/1650] update contrib docs use pip install -e . instead of python setup.py develop install curtsies with above command instead of manually (this should prevent curtsies version problems) change suggested command from bpython-curtsies to bpython --- doc/sphinx/source/contributing.rst | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index a63c685f7..3ae5f6835 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -18,12 +18,13 @@ Getting your development environment set up bpython supports Python 2.6, 2.7, 3.3 and 3.4. The code is written in Python 2 and transformed for Python 3 with 2to3. This means that it's easier -to work on bpython with Python 2 because it's you can use `python setup.py develop` +to work on bpython with Python 2 because you can use ``python setup.py develop`` +or ``pip install -e .`` (`development mode `_ installs by linking to the bpython source instead of copying it over) in a more straightforward way to have your changes immediately reflected by -your bpython installation and the `bpython`, `bpython-curtsies`, and `bpython-urwid` +your bpython installation and the `bpython`, `bpython-curses`, and `bpython-urwid` commands. Using a virtual environment is probably a good idea. Create a virtual environment with @@ -31,7 +32,7 @@ Using a virtual environment is probably a good idea. Create a virtual environmen .. code-block:: bash $ virtualenv bpython-dev # determines Python version used - $ source bpython-dev/bin/activate # necssary every time you work on bpython + $ source bpython-dev/bin/activate # necessary every time you work on bpython Fork bpython in the GitHub web interface, then clone the repo: @@ -40,17 +41,16 @@ Fork bpython in the GitHub web interface, then clone the repo: $ git clone git@github.com:YOUR_GITHUB_USERNAME/bpython.git $ # or "git clone https://github.com/YOUR_GITHUB_USERNAME/bpython.git" -Next install the dependencies and install your development copy of bpython: +Next install the install your development copy of bpython and its dependencies: .. code-block:: bash - $ pip install pygments requests # install required dependencies - $ pip install curtsies greenlet watchdog urwid # install optional dependencies - $ pip install sphinx mock nosetests # development dependencies $ cd bpython - $ python setup.py develop + $ pip install -e . # install bpython and required dependencies + $ pip install watchdog urwid # install optional dependencies + $ pip install sphinx mock nose # development dependencies - $ bpython-curtsies # this runs your modified copy of bpython! + $ bpython # this runs your modified copy of bpython! As a first dev task, I recommend getting `bpython` to print your name every time you hit a specific key. @@ -60,8 +60,8 @@ To run tests from the bpython directory: $ nosetests -To build the docs: ------------------- +Building the documentation +-------------------------- The documentation is included in the bpython repository. After checking out the bpython repository and installing `sphinx` as described in @@ -75,7 +75,7 @@ repository to build the documentation: Afterwards you can point your browser to `doc/sphinx/build/html/index.html`. Don't forget to recreate the HTML after you make changes. -To hack on the site or theme +Hacking on the site or theme ---------------------------- The site (and its theme as well) is stored in a separate repository and built using From 347105eb0cb1dbcbf7c5fb3f0c75ab0dc62647c8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 19:52:45 +0100 Subject: [PATCH 0162/1650] Update requirements Signed-off-by: Sebastian Ramacher --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 95f1fbda3..7fd8f8ae4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Pygments -argparse -curtsies -distribute +curtsies >=0.1.15, <0.2.0 greenlet +setuptools +requests From 19cd412090165abf22b965a1e4db324d3261f7c7 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 9 Jan 2015 15:27:36 -0500 Subject: [PATCH 0163/1650] recommend using a package manager --- doc/sphinx/source/contributing.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 3ae5f6835..bba3fabba 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -52,6 +52,10 @@ Next install the install your development copy of bpython and its dependencies: $ bpython # this runs your modified copy of bpython! +Many requirements are also available from your distribution's package manager. +Installation of some dependencies requires Python headers and a C compiler. +These are also available from your package manager. + As a first dev task, I recommend getting `bpython` to print your name every time you hit a specific key. To run tests from the bpython directory: From e33f6f1af55ab4872161417777bc65b01899c390 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 10 Jan 2015 18:34:37 +0100 Subject: [PATCH 0164/1650] Pack the note about package managers in a note env Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/contributing.rst | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index bba3fabba..0ca85b684 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -52,11 +52,31 @@ Next install the install your development copy of bpython and its dependencies: $ bpython # this runs your modified copy of bpython! -Many requirements are also available from your distribution's package manager. -Installation of some dependencies requires Python headers and a C compiler. -These are also available from your package manager. +.. note:: -As a first dev task, I recommend getting `bpython` to print your name every time you hit a specific key. + Many requirements are also available from your distribution's package + manager. On Debian/Ubuntu based systems, the following packages can be used: + + .. code-block:: bash + + $ sudp apt-get install python-greenlet python-pygments python-requests + $ sudo apt-get install python-watchdog python-urwid + $ sudo apt-get install python-sphinx python-mock python-nose + + Rememeber to replace ``python`` with ``python3`` in every package name if + you intend to develop with Python 3. You also need to run `virtualenv` with + `--system-site-packages` packages, if you want to use the packages provided + by your distribution. + + Installation of some dependencies with ``pip`` requires Python headers and a + C compiler. These are also available from your package manager. + + .. code-block:: bash + + $ sudo apt-get install gcc python-dev + +As a first dev task, I recommend getting `bpython` to print your name every +time you hit a specific key. To run tests from the bpython directory: From a609304b7391cc618a84e5b457f44e0429856079 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 10 Jan 2015 18:36:10 +0100 Subject: [PATCH 0165/1650] Make note about compiler and python-dev seperate note Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/contributing.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 0ca85b684..37d83d7c1 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -68,6 +68,8 @@ Next install the install your development copy of bpython and its dependencies: `--system-site-packages` packages, if you want to use the packages provided by your distribution. +.. note:: + Installation of some dependencies with ``pip`` requires Python headers and a C compiler. These are also available from your package manager. From bf5cf3c704b1e327823aa0d2dcce0085233203b4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 10 Jan 2015 18:41:01 +0100 Subject: [PATCH 0166/1650] Remove a space Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index a63c685f7..222414871 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -109,5 +109,5 @@ included configuration file. After this you can open the `output/index.html` in your favourite browser and see if your changes had an effect. -.. _GitHub issue tracker: https://github.com/bpython/bpython/issues +.. _GitHub issue tracker: https://github.com/bpython/bpython/issues .. _bite-size: https://github.com/bpython/bpython/labels/bitesize From aabcd6d3797e59ec198d4f9721e4bc0230363ba5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 03:50:03 +0100 Subject: [PATCH 0167/1650] Import RefreshReqeustEvent from bpython.curtsiesfrontend.events (fixes #436) Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/interaction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 1036471c1..2d017636c 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -3,7 +3,7 @@ import curtsies.events as events from bpython.repl import Interaction as BpythonInteraction - +from bpython.curtsiesfrontend.events import RefreshRequestEvent from bpython.curtsiesfrontend.manual_readline import edit_keys class StatusBar(BpythonInteraction): @@ -67,7 +67,7 @@ def _check_for_expired_message(self): def process_event(self, e): """Returns True if shutting down""" assert self.in_prompt or self.in_confirm or self.waiting_for_refresh - if isinstance(e, events.RefreshRequestEvent): + if isinstance(e, RefreshRequestEvent): self.waiting_for_refresh = False self.request_greenlet.switch() elif isinstance(e, events.PasteEvent): From 83b80ae60c26ce41ab80bc5ecdda8f19c719146d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 04:03:57 +0100 Subject: [PATCH 0168/1650] Hide watchdog message after some time (fixes #437) Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index b53d39367..f49fec455 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -738,10 +738,9 @@ def toggle_file_watch(self): if self.watching_files: self.watcher.deactivate() self.watching_files = False - self.status_bar.pop_permanent_message(msg) else: self.watching_files = True - self.status_bar.push_permanent_message(msg) + self.status_bar.message(msg) self.watcher.activate() else: self.status_bar.message('Autoreloading not available because watchdog not installed') From 5bf22e4f2f2973fe1b42e02d4e10ae10f9fd6ae5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 05:44:49 +0100 Subject: [PATCH 0169/1650] Implement copy to clipboard (fixes #234) Based on a patch by Keith Dart Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 4 ++ bpython/clipboard.py | 67 +++++++++++++++++++++++++ bpython/config.py | 2 + bpython/curtsiesfrontend/interaction.py | 1 + bpython/curtsiesfrontend/repl.py | 2 + bpython/repl.py | 17 +++++++ 6 files changed, 93 insertions(+) create mode 100644 bpython/clipboard.py diff --git a/bpython/cli.py b/bpython/cli.py index c06602950..cad3a907f 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -962,6 +962,10 @@ def p_key(self, key): self.pastebin() return '' + elif key in key_dispatch[config.copy_clipboard_key]: + self.copy2clipboard() + return '' + elif key in key_dispatch[config.last_output_key]: page(self.stdout_hist[self.prev_block_finished:-4]) return '' diff --git a/bpython/clipboard.py b/bpython/clipboard.py new file mode 100644 index 000000000..25302863d --- /dev/null +++ b/bpython/clipboard.py @@ -0,0 +1,67 @@ +# The MIT License +# +# Copyright (c) 2015 the bpython authors. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import subprocess +import os +import platform +from locale import getpreferredencoding + +class CopyFailed(Exception): + pass + +class XClipboard(object): + """Manage clipboard with xclip.""" + + def copy(self, content): + process = subprocess.Popen(['xclip', '-i', '-selection', 'clipboard'], + stdin=subprocess.PIPE) + process.communicate(content.encode(getpreferredencoding())) + if process.returncode != 0: + raise CopyFailed() + +class OSXClipboard(object): + """Manage clipboard with pbcopy.""" + + def copy(self, content): + process = subprocess.Popen(['pbcopy', 'w'], stdin=subprocess.PIPE) + process.communicate(content.encode(getpreferredencoding())) + if process.returncode != 0: + raise CopyFailed() + +def command_exists(command): + process = subprocess.Popen(['which', command], stderr=subprocess.STDOUT, + stdout=subprocess.PIPE) + process.communicate() + + return process.returncode == 0 + +def get_clipboard(): + """Get best clipboard handling implemention for current system.""" + + if platform.system() == 'Darwin': + if command_exists('pbcopy'): + return OSXClipboard() + if platform.system() in ('Linux', 'FreeBSD', 'OpenBSD') and os.getenv('DISPLAY') is not None: + if command_exists('xclip'): + return XClipboard() + + return None diff --git a/bpython/config.py b/bpython/config.py index 0b0abcb16..5ee3149dc 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -82,6 +82,7 @@ def loadini(struct, configfile): 'edit_current_block': 'C-x', 'help': 'F1', 'last_output': 'F9', + 'copy_clipboard': 'F10', 'pastebin': 'F8', 'save': 'C-s', 'show_source': 'F2', @@ -139,6 +140,7 @@ def get_key_no_doublebind(attr, already_used={}): struct.flush_output = config.getboolean('general', 'flush_output') struct.pastebin_key = get_key_no_doublebind('pastebin') + struct.copy_clipboard_key = get_key_no_doublebind('copy_clipboard') struct.save_key = get_key_no_doublebind('save') struct.search_key = get_key_no_doublebind('search') struct.show_source_key = get_key_no_doublebind('show_source') diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 2d017636c..faf2dfaa3 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -143,6 +143,7 @@ def confirm(self, q): self.prompt = q self.in_confirm = True return self.main_greenlet.switch(q) + def file_prompt(self, s): """Expected to return a file name, given """ self.request_greenlet = greenlet.getcurrent() diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f49fec455..42b988ec9 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -547,6 +547,8 @@ def process_key_event(self, e): greenlet.greenlet(self.write2file).switch() elif e in key_dispatch[self.config.pastebin_key]: # F8 for pastebin greenlet.greenlet(self.pastebin).switch() + elif e in key_dispatch[self.config.copy_clipboard_key]: + greenlet.greenlet(self.copy2clipboard).switch() elif e in key_dispatch[self.config.external_editor_key]: self.send_session_to_external_editor() elif e in key_dispatch[self.config.edit_config_key]: diff --git a/bpython/repl.py b/bpython/repl.py index d8e5d9bba..3d579818c 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -48,6 +48,7 @@ from bpython._py3compat import PythonLexer, py3 from bpython.formatter import Parenthesis from bpython.translations import _ +from bpython.clipboard import get_clipboard, CopyFailed import bpython.autocomplete as autocomplete @@ -446,6 +447,7 @@ def __init__(self, interp, config): # Necessary to fix mercurial.ui.ui expecting sys.stderr to have this # attribute self.closed = False + self.clipboard = get_clipboard() pythonhist = os.path.expanduser(self.config.hist_file) if os.path.exists(pythonhist): @@ -768,6 +770,21 @@ def write2file(self): else: self.interact.notify('Saved to %s.' % (fn, )) + def copy2clipboard(self): + """Copy current content to clipboard.""" + + if self.clipboard is None: + self.interact.notify(_('No clipboard available.')) + return + + content = self.formatforfile(self.getstdout()) + try: + self.clipboard.copy(content) + except CopyFailed: + self.interact.notify(_('Could not copy to clipboard.')) + else: + self.interact.notify(_('Copied content to clipboard.')) + def pastebin(self, s=None): """Upload to a pastebin and display the URL in the status bar.""" From d4db7edf4abb3f65c2d8a6ab072543e9ccb3b0d0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 05:53:45 +0100 Subject: [PATCH 0170/1650] Do not show disabled keyboard shortcuts (fixes #282) Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index cad3a907f..3728b35fe 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1692,12 +1692,19 @@ def init_wins(scr, config): # Thanks to Angus Gibson for pointing out this missing line which was causing # problems that needed dirty hackery to fix. :) - statusbar = Statusbar(scr, main_win, background, config, - _(" <%s> Rewind <%s> Save <%s> Pastebin " - " <%s> Pager <%s> Show Source ") % - (config.undo_key, config.save_key, config.pastebin_key, - config.last_output_key, config.show_source_key), - get_colpair(config, 'main')) + commands = ( + (_('Rewind'), config.undo_key), + (_('Save'), config.save_key), + (_('Pastebin'), config.pastebin_key), + (_('Pager'), config.last_output_key), + (_('Show Source'), config.show_source_key) + ) + + message = ' '.join('<%s> %s' % (key, command) for command, key in commands + if key) + + statusbar = Statusbar(scr, main_win, background, config, message, + get_colpair(config, 'main')) return main_win, statusbar From ee91bf193ca530a6c06f75ab08ae99f78abf7a51 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 06:16:31 +0100 Subject: [PATCH 0171/1650] Use bpython.pager.get_pager_command And use shlex to split PAGER Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 3 ++- bpython/pager.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 42b988ec9..73e5427ac 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -34,6 +34,7 @@ from bpython import translations; translations.init() from bpython.translations import _ from bpython._py3compat import py3 +from bpython.pager import get_pager_command from bpython.curtsiesfrontend import replpainter as paint from bpython.curtsiesfrontend import sitefix; sitefix.monkeypatch_quit() @@ -1350,7 +1351,7 @@ def focus_on_subprocess(self, args): signal.signal(signal.SIGWINCH, prev_sigwinch_handler) def pager(self, text): - command = os.environ.get('PAGER', 'less -rf').split() + command = get_pager_command() with tempfile.NamedTemporaryFile() as tmp: tmp.write(text) tmp.flush() diff --git a/bpython/pager.py b/bpython/pager.py index b397b2827..1a99e8e57 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -27,10 +27,10 @@ import pydoc import subprocess import sys +import shlex - -def get_pager_command(): - command = os.environ.get('PAGER', 'less -r').split() +def get_pager_command(default='less -r'): + command = shlex.split(os.environ.get('PAGER', default)) return command From 2afccdd11cc827a932fbe53a95718c285093b9e1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 07:58:23 +0100 Subject: [PATCH 0172/1650] Use pager for help browser (fixes #294) Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 12 +++++++++ bpython/curtsiesfrontend/_internal.py | 39 +++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 bpython/curtsiesfrontend/_internal.py diff --git a/bpython/curtsies.py b/bpython/curtsies.py index e98b2fd2b..7d562a41c 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -105,6 +105,18 @@ def after_suspend(): after_suspend=after_suspend) as repl: repl.height, repl.width = window.t.height, window.t.width + if interactive: + # Add custom help command + # TODO: add methods to run the code + repl.coderunner.interp.locals['_repl'] = repl + + repl.coderunner.interp.runsource( + 'from bpython.curtsiesfrontend._internal import _Helper') + repl.coderunner.interp.runsource('help = _Helper(_repl)\n') + + del repl.coderunner.interp.locals['_repl'] + del repl.coderunner.interp.locals['_Helper'] + def process_event(e): """If None is passed in, just paint the screen""" try: diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py new file mode 100644 index 000000000..98c1d5c89 --- /dev/null +++ b/bpython/curtsiesfrontend/_internal.py @@ -0,0 +1,39 @@ +# The MIT License +# +# Copyright (c) 2015 the bpython authors. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import sys +import pydoc + +import bpython._internal + +class _Helper(bpython._internal._Helper): + + def __init__(self, repl=None): + self._repl = repl + pydoc.pager = self.pager + + super(_Helper, self).__init__() + + def pager(self, output): + self._repl.pager(output) + +# vim: sw=4 ts=4 sts=4 ai et From 8b2ce14d3f37390cd177800ac87da540df3f0b56 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 20:34:37 +0100 Subject: [PATCH 0173/1650] Add 0.13.2 changelog Signed-off-by: Sebastian Ramacher (cherry picked from commit 27775bf3b995f6fbda746a8c37276bf5e4615905) Conflicts: doc/sphinx/source/changelog.rst --- CHANGELOG | 10 +++++++++- doc/sphinx/source/changelog.rst | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 33a74bd5d..c789c5e9c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,15 @@ Changelog ========= -v0.13.1 +0.13.2 +------- + +A bugfix release. The fixed bugs are: + +* #424: Use new JSON API at bpaste.net. +* #430: Fixed SNI issues with new pastebin service on Mac OS X. + +0.13.1 ------- A bugfix release. The fixed bugs are: diff --git a/doc/sphinx/source/changelog.rst b/doc/sphinx/source/changelog.rst index 6a8f25bed..e766e56f2 100644 --- a/doc/sphinx/source/changelog.rst +++ b/doc/sphinx/source/changelog.rst @@ -1,6 +1,30 @@ Changelog ========= +0.13.2 +------ + +A bugfix release. The fixed bugs are: + +* #424: Use new JSON API at bpaste.net. +* #430: Fixed SNI issues with new pastebin service on Mac OS X. + +0.13.1 +------ + +A bugfix release. The fixed bugs are: + +* #287: Turned off dictionary completion in bpython-curtsies +* #281: Fixed a crash on error-raising properties +* #286: Fixed input in Python 3 +* #293: Added encoding attribute to stdin bpython curtsies +* #296: Fixed warnings in import completion for Python 3 +* #290: Stop using root logger +* #301: Specify curtsies version in requirements + +There's also a necessary regression: #232 (adding fileno() on stdin) +is reintroduced because its previous fix was found to be the cause of #286 + 0.13 ---- From 1509a5486610db9ad4fffacfe87e96890194ff8b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 9 Jan 2015 21:07:19 +0100 Subject: [PATCH 0174/1650] Add #432 to changelog Signed-off-by: Sebastian Ramacher (cherry picked from commit b30971bd4f644150e845e9c35ffb22d6312b456d) --- CHANGELOG | 2 ++ doc/sphinx/source/changelog.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c789c5e9c..68275fd4c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,8 @@ A bugfix release. The fixed bugs are: * #424: Use new JSON API at bpaste.net. * #430: Fixed SNI issues with new pastebin service on Mac OS X. +* #432: Fixed crash in bpython-curtsies in special circumstances if history file + is empty. Thanks to Lisa van Gelder. 0.13.1 ------- diff --git a/doc/sphinx/source/changelog.rst b/doc/sphinx/source/changelog.rst index e766e56f2..da8b74e75 100644 --- a/doc/sphinx/source/changelog.rst +++ b/doc/sphinx/source/changelog.rst @@ -8,6 +8,8 @@ A bugfix release. The fixed bugs are: * #424: Use new JSON API at bpaste.net. * #430: Fixed SNI issues with new pastebin service on Mac OS X. +* #432: Fixed crash in bpython-curtsies in special circumstances if history file + is empty. Thanks to Lisa van Gelder. 0.13.1 ------ From f9c5e7d2e359edf0e2d1f1a45bdad987a7b0f525 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 17:38:44 +0100 Subject: [PATCH 0175/1650] Move History to seperate file and improve error handling (fixes #434) A new method append_reload_and_write is added that handles read and write failures better. Signed-off-by: Sebastian Ramacher --- bpython/history.py | 223 +++++++++++++++++++++++++++++++++++++++++++++ bpython/repl.py | 168 +++------------------------------- 2 files changed, 236 insertions(+), 155 deletions(-) create mode 100644 bpython/history.py diff --git a/bpython/history.py b/bpython/history.py new file mode 100644 index 000000000..48055999e --- /dev/null +++ b/bpython/history.py @@ -0,0 +1,223 @@ +# The MIT License +# +# Copyright (c) 2009-2015 the bpython authors. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + + +import codecs +import os + +from bpython.translations import _ + + +class History(object): + """Stores readline-style history and current place in it""" + + def __init__(self, entries=None, duplicates=True, hist_size=100): + if entries is None: + self.entries = [''] + else: + self.entries = list(entries) + self.index = 0 # how many lines back in history is currently selected + # where 0 is the saved typed line, 1 the prev entered line + self.saved_line = '' # what was on the prompt before using history + self.duplicates = duplicates + self.hist_size = hist_size + + + def append(self, line): + self.append_to(self.entries, line) + + + def append_to(self, entries, line): + line = line.rstrip('\n') + if line: + if not self.duplicates: + # remove duplicates + try: + while True: + entries.remove(line) + except ValueError: + pass + entries.append(line) + + + def first(self): + """Move back to the beginning of the history.""" + if not self.is_at_end: + self.index = len(self.entries) + return self.entries[-self.index] + + + def back(self, start=True, search=False, target=None, include_current=False): + """Move one step back in the history.""" + if target is None: + target = self.saved_line + if not self.is_at_end: + if search: + self.index += self.find_partial_match_backward(target, include_current) + elif start: + self.index += self.find_match_backward(target, include_current) + else: + self.index += 1 + return self.entry + + + @property + def entry(self): + """The current entry, which may be the saved line""" + return self.entries[-self.index] if self.index else self.saved_line + + + @property + def entries_by_index(self): + return list(reversed(self.entries + [self.saved_line])) + + + def find_match_backward(self, search_term, include_current=False): + for idx, val in enumerate(self.entries_by_index[self.index + (0 if include_current else 1):]): + if val.startswith(search_term): + return idx + (0 if include_current else 1) + return 0 + + + def find_partial_match_backward(self, search_term, include_current=False): + for idx, val in enumerate(self.entries_by_index[self.index + (0 if include_current else 1):]): + if search_term in val: + return idx + (0 if include_current else 1) + return 0 + + + def forward(self, start=True, search=False, target=None, include_current=False): + """Move one step forward in the history.""" + if target is None: + target = self.saved_line + if self.index > 1: + if search: + self.index -= self.find_partial_match_forward(target, include_current) + elif start: + self.index -= self.find_match_forward(target, include_current) + else: + self.index -= 1 + return self.entry + else: + self.index = 0 + return self.saved_line + + + def find_match_forward(self, search_term, include_current=False): + #TODO these are no longer efficient, because we realize the whole list. Does this matter? + for idx, val in enumerate(reversed(self.entries_by_index[:max(0, self.index - (1 if include_current else 0))])): + if val.startswith(search_term): + return idx + (0 if include_current else 1) + return self.index + + + def find_partial_match_forward(self, search_term, include_current=False): + for idx, val in enumerate(reversed(self.entries_by_index[:max(0, self.index - (1 if include_current else 0))])): + if search_term in val: + return idx + (0 if include_current else 1) + return self.index + + + def last(self): + """Move forward to the end of the history.""" + if not self.is_at_start: + self.index = 0 + return self.entries[0] + + + @property + def is_at_end(self): + return self.index >= len(self.entries) or self.index == -1 + + + @property + def is_at_start(self): + return self.index == 0 + + + def enter(self, line): + if self.index == 0: + self.saved_line = line + + + @classmethod + def from_filename(cls, filename): + history = cls() + history.load(filename) + return history + + + def reset(self): + self.index = 0 + self.saved_line = '' + + + def load(self, filename, encoding): + with codecs.open(filename, 'r', encoding, 'ignore') as hfile: + self.entries = self.load_from(hfile) + + + def load_from(self, fd): + entries = [] + for line in fd: + self.append_to(entries, line) + return entries + + + def save(self, filename, encoding, lines=0): + with codecs.open(filename, 'w', encoding, 'ignore') as hfile: + self.save_to(hfile, self.entries, lines) + + + def save_to(self, fd, entries=None, lines=0): + if entries is None: + entries = self.entries + for line in entries[-lines:]: + fd.write(line) + fd.write('\n') + + + def append_reload_and_write(self, s, filename, encoding): + if not self.hist_size: + return self.append(s) + + try: + with codecs.open(filename, 'rw+', encoding, 'ignore') as hfile: + # read entries + hfile.seek(0, os.SEEK_SET) + entries = self.load_from(hfile) + self.append_to(entries, s) + + # write new entries + hfile.seek(0, os.SEEK_SET) + hfile.truncate() + self.save_to(hfile, entries, self.hist_size) + + self.entries = entries + except EnvironmentError as err: + raise RuntimeError(_('Error occurded while writing to file %s (%s)') + % (filename, err.strerror)) + else: + if len(self.entries) == 0: + # Make sure that entries contains at least one element. If the + # file and s are empty, this can occur. + self.entries = [''] diff --git a/bpython/repl.py b/bpython/repl.py index 3d579818c..31c7d4b49 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -49,6 +49,7 @@ from bpython.formatter import Parenthesis from bpython.translations import _ from bpython.clipboard import get_clipboard, CopyFailed +from bpython.history import History import bpython.autocomplete as autocomplete @@ -137,143 +138,6 @@ def writetb(self, lines): self.write(line) -class History(object): - """Stores readline-style history and current place in it""" - - def __init__(self, entries=None, duplicates=False): - if entries is None: - self.entries = [''] - else: - self.entries = list(entries) - self.index = 0 # how many lines back in history is currently selected - # where 0 is the saved typed line, 1 the prev entered line - self.saved_line = '' # what was on the prompt before using history - self.duplicates = duplicates - - def append(self, line): - line = line.rstrip('\n') - if line: - if not self.duplicates: - # remove duplicates - try: - while True: - self.entries.remove(line) - except ValueError: - pass - self.entries.append(line) - - def first(self): - """Move back to the beginning of the history.""" - if not self.is_at_end: - self.index = len(self.entries) - return self.entries[-self.index] - - def back(self, start=True, search=False, target=None, include_current=False): - """Move one step back in the history.""" - if target is None: - target = self.saved_line - if not self.is_at_end: - if search: - self.index += self.find_partial_match_backward(target, include_current) - elif start: - self.index += self.find_match_backward(target, include_current) - else: - self.index += 1 - return self.entry - - @property - def entry(self): - """The current entry, which may be the saved line""" - return self.entries[-self.index] if self.index else self.saved_line - - @property - def entries_by_index(self): - return list(reversed(self.entries + [self.saved_line])) - - def find_match_backward(self, search_term, include_current=False): - for idx, val in enumerate(self.entries_by_index[self.index + (0 if include_current else 1):]): - if val.startswith(search_term): - return idx + (0 if include_current else 1) - return 0 - - def find_partial_match_backward(self, search_term, include_current=False): - for idx, val in enumerate(self.entries_by_index[self.index + (0 if include_current else 1):]): - if search_term in val: - return idx + (0 if include_current else 1) - return 0 - - - def forward(self, start=True, search=False, target=None, include_current=False): - """Move one step forward in the history.""" - if target is None: - target = self.saved_line - if self.index > 1: - if search: - self.index -= self.find_partial_match_forward(target, include_current) - elif start: - self.index -= self.find_match_forward(target, include_current) - else: - self.index -= 1 - return self.entry - else: - self.index = 0 - return self.saved_line - - def find_match_forward(self, search_term, include_current=False): - #TODO these are no longer efficient, because we realize the whole list. Does this matter? - for idx, val in enumerate(reversed(self.entries_by_index[:max(0, self.index - (1 if include_current else 0))])): - if val.startswith(search_term): - return idx + (0 if include_current else 1) - return self.index - - def find_partial_match_forward(self, search_term, include_current=False): - for idx, val in enumerate(reversed(self.entries_by_index[:max(0, self.index - (1 if include_current else 0))])): - if search_term in val: - return idx + (0 if include_current else 1) - return self.index - - - - def last(self): - """Move forward to the end of the history.""" - if not self.is_at_start: - self.index = 0 - return self.entries[0] - - @property - def is_at_end(self): - return self.index >= len(self.entries) or self.index == -1 - - @property - def is_at_start(self): - return self.index == 0 - - def enter(self, line): - if self.index == 0: - self.saved_line = line - - @classmethod - def from_filename(cls, filename): - history = cls() - history.load(filename) - return history - - def load(self, filename, encoding): - with codecs.open(filename, 'r', encoding, 'ignore') as hfile: - for line in hfile: - self.append(line) - - def reset(self): - self.index = 0 - self.saved_line = '' - - def save(self, filename, encoding, lines=0): - with codecs.open(filename, 'w', encoding, 'ignore') as hfile: - for line in self.entries[-lines:]: - hfile.write(line) - hfile.write('\n') - - class MatchesIterator(object): """Stores a list of matches and which one is currently selected if any. @@ -429,7 +293,8 @@ def __init__(self, interp, config): self.interp = interp self.interp.syntaxerror_callback = self.clear_current_line self.match = False - self.rl_history = History(duplicates=config.hist_duplicates) + self.rl_history = History(duplicates=config.hist_duplicates, + hist_size=config.hist_length) self.s_hist = [] self.history = [] self.evaluating = False @@ -451,8 +316,11 @@ def __init__(self, interp, config): pythonhist = os.path.expanduser(self.config.hist_file) if os.path.exists(pythonhist): - self.rl_history.load(pythonhist, - getpreferredencoding() or "ascii") + try: + self.rl_history.load(pythonhist, + getpreferredencoding() or "ascii") + except EnvironmentError: + pass @property def ps1(self): @@ -905,21 +773,11 @@ def push(self, s, insert_into_history=True): return more def insert_into_history(self, s): - if self.config.hist_length: - histfilename = self.config.hist_file - oldhistory = self.rl_history.entries - self.rl_history.entries = [] - if os.path.exists(histfilename): - self.rl_history.load(histfilename, getpreferredencoding()) - self.rl_history.append(s) - try: - self.rl_history.save(histfilename, getpreferredencoding(), self.config.hist_length) - except EnvironmentError as err: - self.interact.notify("Error occured while writing to file %s (%s) " % (histfilename, err.strerror)) - self.rl_history.entries = oldhistory - self.rl_history.append(s) - else: - self.rl_history.append(s) + try: + self.rl_history.append_reload_and_write(s, self.config.hist_file, + getpreferredencoding()) + except RuntimeError as e: + self.interact.notify(str(e)) def undo(self, n=1): """Go back in the undo history n steps and call reeavluate() From 0b626d4aeac02fe6837fd5493d0e9c172b480071 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 17:41:16 +0100 Subject: [PATCH 0176/1650] Add file locking for history file Signed-off-by: Sebastian Ramacher --- bpython/filelock.py | 50 +++++++++++++++++++++++++++++++++++++++++++++ bpython/history.py | 30 +++++++++++++++------------ 2 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 bpython/filelock.py diff --git a/bpython/filelock.py b/bpython/filelock.py new file mode 100644 index 000000000..532cb4b7c --- /dev/null +++ b/bpython/filelock.py @@ -0,0 +1,50 @@ +# The MIT License +# +# Copyright (c) 2015 the bpython authors. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + + +try: + import fcntl + has_fcntl = True +except ImportError: + has_fcntl = False + + +class FileLock(object): + """Simple file locking + + On platforms without fcntl, all operations in this class are no-ops. + """ + + def __init__(self, fd, mode=fcntl.LOCK_EX): + self.fd = fd + self.mode = mode + + def __enter__(self): + if has_fcntl: + fcntl.flock(self.fd, self.mode) + return self + + def __exit__(self, *args): + if has_fcntl: + fcntl.flock(self.fd, fcntl.LOCK_UN) + +# vim: sw=4 ts=4 sts=4 ai et diff --git a/bpython/history.py b/bpython/history.py index 48055999e..1ff58e75e 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -25,6 +25,7 @@ import os from bpython.translations import _ +from bpython.filelock import FileLock class History(object): @@ -173,7 +174,8 @@ def reset(self): def load(self, filename, encoding): with codecs.open(filename, 'r', encoding, 'ignore') as hfile: - self.entries = self.load_from(hfile) + with FileLock(hfile) as lock: + self.entries = self.load_from(hfile) def load_from(self, fd): @@ -185,7 +187,8 @@ def load_from(self, fd): def save(self, filename, encoding, lines=0): with codecs.open(filename, 'w', encoding, 'ignore') as hfile: - self.save_to(hfile, self.entries, lines) + with FileLock(hfile) as lock: + self.save_to(hfile, self.entries, lines) def save_to(self, fd, entries=None, lines=0): @@ -202,17 +205,18 @@ def append_reload_and_write(self, s, filename, encoding): try: with codecs.open(filename, 'rw+', encoding, 'ignore') as hfile: - # read entries - hfile.seek(0, os.SEEK_SET) - entries = self.load_from(hfile) - self.append_to(entries, s) - - # write new entries - hfile.seek(0, os.SEEK_SET) - hfile.truncate() - self.save_to(hfile, entries, self.hist_size) - - self.entries = entries + with FileLock(hfile) as lock: + # read entries + hfile.seek(0, os.SEEK_SET) + entries = self.load_from(hfile) + self.append_to(entries, s) + + # write new entries + hfile.seek(0, os.SEEK_SET) + hfile.truncate() + self.save_to(hfile, entries, self.hist_size) + + self.entries = entries except EnvironmentError as err: raise RuntimeError(_('Error occurded while writing to file %s (%s)') % (filename, err.strerror)) From 254aeaf767127a828c579c684d1d4cddc8fc5463 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 18:02:49 +0100 Subject: [PATCH 0177/1650] Use correct file mode Signed-off-by: Sebastian Ramacher --- bpython/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/history.py b/bpython/history.py index 1ff58e75e..9a40d3cdd 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -204,7 +204,7 @@ def append_reload_and_write(self, s, filename, encoding): return self.append(s) try: - with codecs.open(filename, 'rw+', encoding, 'ignore') as hfile: + with codecs.open(filename, 'a+', encoding, 'ignore') as hfile: with FileLock(hfile) as lock: # read entries hfile.seek(0, os.SEEK_SET) From 22cb3caa562c1f0ee2578049f149803f5fe0b03c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 22:00:18 +0100 Subject: [PATCH 0178/1650] EOL 80 Signed-off-by: Sebastian Ramacher --- bpython/history.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bpython/history.py b/bpython/history.py index 9a40d3cdd..5771444c6 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -37,7 +37,8 @@ def __init__(self, entries=None, duplicates=True, hist_size=100): else: self.entries = list(entries) self.index = 0 # how many lines back in history is currently selected - # where 0 is the saved typed line, 1 the prev entered line + # where 0 is the saved typed line, 1 the prev entered + # line self.saved_line = '' # what was on the prompt before using history self.duplicates = duplicates self.hist_size = hist_size @@ -67,13 +68,15 @@ def first(self): return self.entries[-self.index] - def back(self, start=True, search=False, target=None, include_current=False): + def back(self, start=True, search=False, target=None, + include_current=False): """Move one step back in the history.""" if target is None: target = self.saved_line if not self.is_at_end: if search: - self.index += self.find_partial_match_backward(target, include_current) + self.index += self.find_partial_match_backward(target, + include_current) elif start: self.index += self.find_match_backward(target, include_current) else: @@ -106,7 +109,8 @@ def find_partial_match_backward(self, search_term, include_current=False): return 0 - def forward(self, start=True, search=False, target=None, include_current=False): + def forward(self, start=True, search=False, target=None, + include_current=False): """Move one step forward in the history.""" if target is None: target = self.saved_line From 35cb7c449053ab872b570ed26183e073a823ec0b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 22:00:58 +0100 Subject: [PATCH 0179/1650] Do not realize the whole entry list Signed-off-by: Sebastian Ramacher --- bpython/history.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/bpython/history.py b/bpython/history.py index 5771444c6..1ce144d57 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -23,6 +23,7 @@ import codecs import os +from itertools import islice from bpython.translations import _ from bpython.filelock import FileLock @@ -96,16 +97,20 @@ def entries_by_index(self): def find_match_backward(self, search_term, include_current=False): - for idx, val in enumerate(self.entries_by_index[self.index + (0 if include_current else 1):]): + add = 0 if include_current else 1 + start = self.index + add + for idx, val in enumerate(islice(self.entries_by_index, start, None)): if val.startswith(search_term): - return idx + (0 if include_current else 1) + return idx + add return 0 def find_partial_match_backward(self, search_term, include_current=False): - for idx, val in enumerate(self.entries_by_index[self.index + (0 if include_current else 1):]): + add = 0 if include_current else 1 + start = self.index + add + for idx, val in enumerate(islice(self.entries_by_index, start, None)): if search_term in val: - return idx + (0 if include_current else 1) + return idx + add return 0 @@ -116,7 +121,8 @@ def forward(self, start=True, search=False, target=None, target = self.saved_line if self.index > 1: if search: - self.index -= self.find_partial_match_forward(target, include_current) + self.index -= self.find_partial_match_forward(target, + include_current) elif start: self.index -= self.find_match_forward(target, include_current) else: @@ -128,17 +134,22 @@ def forward(self, start=True, search=False, target=None, def find_match_forward(self, search_term, include_current=False): - #TODO these are no longer efficient, because we realize the whole list. Does this matter? - for idx, val in enumerate(reversed(self.entries_by_index[:max(0, self.index - (1 if include_current else 0))])): + add = 0 if include_current else 1 + end = max(0, self.index - (1 - add)) + for idx in xrange(end): + val = self.entries_by_index[end - 1 - idx] if val.startswith(search_term): return idx + (0 if include_current else 1) return self.index def find_partial_match_forward(self, search_term, include_current=False): - for idx, val in enumerate(reversed(self.entries_by_index[:max(0, self.index - (1 if include_current else 0))])): + add = 0 if include_current else 1 + end = max(0, self.index - (1 - add)) + for idx in xrange(end): + val = self.entries_by_index[end - 1 - idx] if search_term in val: - return idx + (0 if include_current else 1) + return idx + add return self.index From a8be1281f9651b6d52feef03afffeaa5aa432692 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 22:14:01 +0100 Subject: [PATCH 0180/1650] Split History tests Signed-off-by: Sebastian Ramacher --- bpython/test/test_history.py | 76 ++++++++++++++++++++++++++++++++++++ bpython/test/test_repl.py | 70 --------------------------------- 2 files changed, 76 insertions(+), 70 deletions(-) create mode 100644 bpython/test/test_history.py diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py new file mode 100644 index 000000000..88881100e --- /dev/null +++ b/bpython/test/test_history.py @@ -0,0 +1,76 @@ +try: + import unittest2 as unittest +except ImportError: + import unittest + +from bpython.history import History + +class TestHistory(unittest.TestCase): + def setUp(self): + self.history = History('#%d' % x for x in range(1000)) + + def test_is_at_start(self): + self.history.first() + + self.assertNotEqual(self.history.index, 0) + self.assertTrue(self.history.is_at_end) + self.history.forward() + self.assertFalse(self.history.is_at_end) + + def test_is_at_end(self): + self.history.last() + + self.assertEqual(self.history.index, 0) + self.assertTrue(self.history.is_at_start) + self.assertFalse(self.history.is_at_end) + + def test_first(self): + self.history.first() + + self.assertFalse(self.history.is_at_start) + self.assertTrue(self.history.is_at_end) + + def test_last(self): + self.history.last() + + self.assertTrue(self.history.is_at_start) + self.assertFalse(self.history.is_at_end) + + def test_back(self): + self.assertEqual(self.history.back(), '#999') + self.assertNotEqual(self.history.back(), '#999') + self.assertEqual(self.history.back(), '#997') + for x in range(997): + self.history.back() + self.assertEqual(self.history.back(), '#0') + + def test_forward(self): + self.history.first() + + self.assertEqual(self.history.forward(), '#1') + self.assertNotEqual(self.history.forward(), '#1') + self.assertEqual(self.history.forward(), '#3') + # 1000 == entries 4 == len(range(1, 3) ===> '#1000' (so +1) + for x in range(1000 - 4 - 1): + self.history.forward() + self.assertEqual(self.history.forward(), '#999') + + def test_append(self): + self.history.append('print "foo\n"\n') + self.history.append('\n') + + self.assertEqual(self.history.back(), 'print "foo\n"') + + @unittest.skip("I don't understand this test") + def test_enter(self): + self.history.enter('#lastnumber!') + + self.assertEqual(self.history.back(), '#999') + self.assertEqual(self.history.forward(), '#lastnumber!') + + def test_reset(self): + self.history.enter('#lastnumber!') + self.history.reset() + + self.assertEqual(self.history.back(), '#999') + self.assertEqual(self.history.forward(), '') diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 3b246f29a..33b388497 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -43,76 +43,6 @@ def __init__(self): self.cpos = 0 self.rl_history = FakeHistory() -class TestHistory(unittest.TestCase): - def setUp(self): - self.history = repl.History('#%d' % x for x in range(1000)) - - def test_is_at_start(self): - self.history.first() - - self.assertNotEqual(self.history.index, 0) - self.assertTrue(self.history.is_at_end) - self.history.forward() - self.assertFalse(self.history.is_at_end) - - def test_is_at_end(self): - self.history.last() - - self.assertEqual(self.history.index, 0) - self.assertTrue(self.history.is_at_start) - self.assertFalse(self.history.is_at_end) - - def test_first(self): - self.history.first() - - self.assertFalse(self.history.is_at_start) - self.assertTrue(self.history.is_at_end) - - def test_last(self): - self.history.last() - - self.assertTrue(self.history.is_at_start) - self.assertFalse(self.history.is_at_end) - - def test_back(self): - self.assertEqual(self.history.back(), '#999') - self.assertNotEqual(self.history.back(), '#999') - self.assertEqual(self.history.back(), '#997') - for x in range(997): - self.history.back() - self.assertEqual(self.history.back(), '#0') - - def test_forward(self): - self.history.first() - - self.assertEqual(self.history.forward(), '#1') - self.assertNotEqual(self.history.forward(), '#1') - self.assertEqual(self.history.forward(), '#3') - # 1000 == entries 4 == len(range(1, 3) ===> '#1000' (so +1) - for x in range(1000 - 4 - 1): - self.history.forward() - self.assertEqual(self.history.forward(), '#999') - - def test_append(self): - self.history.append('print "foo\n"\n') - self.history.append('\n') - - self.assertEqual(self.history.back(), 'print "foo\n"') - - @unittest.skip("I don't understand this test") - def test_enter(self): - self.history.enter('#lastnumber!') - - self.assertEqual(self.history.back(), '#999') - self.assertEqual(self.history.forward(), '#lastnumber!') - - def test_reset(self): - self.history.enter('#lastnumber!') - self.history.reset() - - self.assertEqual(self.history.back(), '#999') - self.assertEqual(self.history.forward(), '') - class TestMatchesIterator(unittest.TestCase): def setUp(self): From 5725b53e0ab6a35db68d86b96153e8f3917ee212 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 22:22:19 +0100 Subject: [PATCH 0181/1650] Fix History.enter test cases Signed-off-by: Sebastian Ramacher --- bpython/test/test_history.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index 88881100e..2362cef8d 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -61,13 +61,20 @@ def test_append(self): self.assertEqual(self.history.back(), 'print "foo\n"') - @unittest.skip("I don't understand this test") def test_enter(self): self.history.enter('#lastnumber!') - self.assertEqual(self.history.back(), '#999') + self.assertEqual(self.history.back(), '#lastnumber!') self.assertEqual(self.history.forward(), '#lastnumber!') + def test_enter_2(self): + self.history.enter('#50') + + self.assertEqual(self.history.back(), '#509') + self.assertEqual(self.history.back(), '#508') + self.assertEqual(self.history.forward(), '#509') + + def test_reset(self): self.history.enter('#lastnumber!') self.history.reset() From 566dfe5a5fab815fdd8d04d6d3d131333c2a5800 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 23:16:09 +0100 Subject: [PATCH 0182/1650] Enable option to disable Unicode box (fixes #254) Signed-off-by: Sebastian Ramacher --- bpython/config.py | 26 ++++++++++++++++++++++++- bpython/curtsiesfrontend/replpainter.py | 12 ++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 5ee3149dc..b17cfa047 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + from __future__ import with_statement import os import sys @@ -60,7 +62,8 @@ def loadini(struct, configfile): 'pastebin_expiry': '1week', 'pastebin_helper': '', 'save_append_py': False, - 'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')) + 'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')), + 'unicode_box': True }, 'keyboard': { 'backspace': 'C-h', @@ -240,6 +243,27 @@ def get_key_no_doublebind(attr, already_used={}): if struct.autocomplete_mode not in bpython.autocomplete.ALL_MODES: struct.autocomplete_mode = default_completion + # set box drawing characters + if config.getboolean('general', 'unicode_box'): + struct.left_border = u'│' + struct.right_border = u'│' + struct.top_border = u'─' + struct.bottom_border = u'─' + struct.left_bottom_corner = u'└' + struct.right_bottom_corner = u'┘' + struct.left_top_corner = u'┌' + struct.right_top_corner = u'┐' + else: + struct.left_border = u'|' + struct.right_border = u'|' + struct.top_border = u'-' + struct.bottom_border = u'-' + struct.left_bottom_corner = u'+' + struct.right_bottom_corner = u'+' + struct.left_top_corner = u'+' + struct.right_top_corner = u'+' + + def load_theme(struct, path, colors, default_colors): theme = ConfigParser() with open(path, 'r') as f: diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index b5141cefa..299287070 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -154,10 +154,10 @@ def paint_infobox(rows, columns, matches, argspec, match, docstring, config, for output_lines = [] border_color = func_for_letter(config.color_scheme['main']) - output_lines.append(border_color(u'┌─'+u'─'*width+u'─┐')) + output_lines.append(border_color(config.left_top_corner+config.top_border*(width+2)+config.right_top_corner)) for line in lines: - output_lines.append(border_color(u'│ ')+((line+' '*(width - len(line)))[:width])+border_color(u' │')) - output_lines.append(border_color(u'└─'+u'─'*width+u'─┘')) + output_lines.append(border_color(config.left_border+u' ')+((line+' '*(width - len(line)))[:width])+border_color(u' ' + config.right_border)) + output_lines.append(border_color(config.left_bottom_corner+config.bottom_border*(width+2)+config.right_bottom_corner)) r = fsarray(output_lines[:min(rows-1, len(output_lines)-1)] + output_lines[-1:]) return r @@ -166,10 +166,10 @@ def paint_last_events(rows, columns, names): return fsarray([]) width = min(max(len(name) for name in names), columns-2) output_lines = [] - output_lines.append(u'┌'+u'─'*width+u'┐') + output_lines.append(config.left_top_corner+config.top_border*(width+2)+config.right_top_corner) for name in names[-(rows-2):]: - output_lines.append(u'│'+name[:width].center(width)+u'│') - output_lines.append(u'└'+u'─'*width+u'┘') + output_lines.append(config.left_border+name[:width].center(width)+config.right_border) + output_lines.append(config.left_bottom_corner+config.bottom_border*width+config.right_bottom_corner) return fsarray(output_lines) def paint_statusbar(rows, columns, msg, config): From af3f58d1c0b84dee6d7ba40b3e188274bafb7d6f Mon Sep 17 00:00:00 2001 From: bobf Date: Mon, 12 Jan 2015 09:25:31 +0000 Subject: [PATCH 0183/1650] Fix syntax error in README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7fc8fc258..bf7a75f33 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ Dependencies * requests * curtsies >= 0.1.15,<0.2.0 * greenlet -* watchdog (optional, (for monitoring imported modules for changes) +* watchdog (optional, (for monitoring imported modules for changes)) bpython-urwid ------------- From 68a0491d9b27cbf9612fa5a0d5d0d4070c94bd37 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Jan 2015 23:18:49 +0100 Subject: [PATCH 0184/1650] Document unicode_box Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/configuration-options.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index d51443d02..b18358914 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -147,6 +147,12 @@ Editor for externally editing the current line. .. versionadded:: 0.13 +unicode_box +^^^^^^^^^^^ +Whether to use Unicode characters to draw boxes. + +.. versionadded:: 0.14 + Keyboard -------- This section refers to the ``[keyboard]`` section in your From 0f6fa49fe7bd38611d079cf724d0cc8b9c38500c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 12 Jan 2015 18:57:52 +0100 Subject: [PATCH 0185/1650] Less parens Signed-off-by: Sebastian Ramacher --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index bf7a75f33..7507b8de9 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ Dependencies * requests * curtsies >= 0.1.15,<0.2.0 * greenlet -* watchdog (optional, (for monitoring imported modules for changes)) +* watchdog (optional, for monitoring imported modules for changes) bpython-urwid ------------- From b88b69019537e30c86b6128c9641cb621581ce8d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 12 Jan 2015 18:59:56 +0100 Subject: [PATCH 0186/1650] Move required dependencies to the top Also remove those apt-gets. Signed-off-by: Sebastian Ramacher --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 7507b8de9..05230ed84 100644 --- a/README.rst +++ b/README.rst @@ -8,13 +8,13 @@ bpython - A fancy curses interface to the Python interactive interpreter Dependencies ============ -* Pygments (apt-get install python-pygments) -* Sphinx != 1.1.2 (for the documentation only) (apt-get install python-sphinx) -* mock (for the testsuite only) -* babel (optional, for internationalization) +* Pygments * requests * curtsies >= 0.1.15,<0.2.0 * greenlet +* Sphinx != 1.1.2 (optional, for the documentation) +* mock (optional, for the testsuite) +* babel (optional, for internationalization) * watchdog (optional, for monitoring imported modules for changes) bpython-urwid From 1b698fbbf25e27cb790deafe10bdd7c3ef20f91f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 12 Jan 2015 23:04:13 +0100 Subject: [PATCH 0187/1650] Update tips and tricks Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/tips.rst | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/doc/sphinx/source/tips.rst b/doc/sphinx/source/tips.rst index 58df274c7..d6ebdb433 100644 --- a/doc/sphinx/source/tips.rst +++ b/doc/sphinx/source/tips.rst @@ -2,8 +2,8 @@ Tips and tricks =============== -There are various tricks and tips to bpython. We currently list one of -them on this page. If you know any more. Don't hesitate to let us know +There are various tricks and tips to bpython. We currently list one of them on +this page. If you know any more, don't hesitate to let us know (:ref:`community`)! bpython and multiple python versions @@ -14,11 +14,22 @@ to us by Simon Liedtke. Do a source checkout of bpython and add the following to your `.profile` equivalent file. +.. code-block:: bash + alias bpython2.6='PYTHONPATH=~/python/bpython python2.6 -m bpython.cli' Where the `~/python/bpython`-path is the path to where your bpython source code resides. -You can offcourse add multiple aliasses (make sure you have pygments installed -on all python versions though), so you can run bpython with 2.6, 2.7 and the 3 -series. +.. note:: + + If you want to create the alias for Python 3.X, make sure to run `python3.X + setup.py build` first and point `PYTHONPATH` to the build location (usually + `build/lib`). + +You can of course add multiple aliases, so you can run bpython with 2.6, 2.7 +and the 3 series. + +.. note:: + + Make sure you have the dependencies installed on all Python versions. From acf8e5c12e017b8647b88a443f7dc52ad7f79b5a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 12 Jan 2015 23:27:09 +0100 Subject: [PATCH 0188/1650] Also test build documentation Signed-off-by: Sebastian Ramacher --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ea7912977..0759ff2d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,5 +20,6 @@ install: - python setup.py install script: + - python setup.py build_sphinx - cd build/lib/ - - "nosetests bpython/test" + - nosetests bpython/test From a21962f04f7abf1117e76f4f4e2954008d24c69c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 12 Jan 2015 23:35:54 +0100 Subject: [PATCH 0189/1650] Remove empty test Signed-off-by: Sebastian Ramacher --- bpython/test/test_wizard.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 bpython/test/test_wizard.py diff --git a/bpython/test/test_wizard.py b/bpython/test/test_wizard.py deleted file mode 100644 index e69de29bb..000000000 From a8701a53077ae42e505950e84f9e9767f2b4ca87 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 12 Jan 2015 23:53:34 +0100 Subject: [PATCH 0190/1650] Move the logo to data We do not use it in bpython directly anymore. Let's use it for the .desktop file now. Signed-off-by: Sebastian Ramacher --- data/bpython.desktop | 2 +- bpython/logo.png => data/bpython.png | Bin setup.py | 5 +++-- 3 files changed, 4 insertions(+), 3 deletions(-) rename bpython/logo.png => data/bpython.png (100%) diff --git a/data/bpython.desktop b/data/bpython.desktop index 09907029e..bdb121590 100644 --- a/data/bpython.desktop +++ b/data/bpython.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Icon=/usr/share/pixmaps/python.xpm +Icon=bpython Name=bpython Comment=A fancy interface to the python interpreter! Exec=/usr/bin/bpython diff --git a/bpython/logo.png b/data/bpython.png similarity index 100% rename from bpython/logo.png rename to data/bpython.png diff --git a/setup.py b/setup.py index 930a6ab92..d83f3ee36 100755 --- a/setup.py +++ b/setup.py @@ -157,7 +157,9 @@ def initialize_options(self): # desktop shortcut (os.path.join('share', 'applications'), ['data/bpython.desktop']), # AppData - (os.path.join('share', 'appdata'), ['data/bpython.appdata.xml']) + (os.path.join('share', 'appdata'), ['data/bpython.appdata.xml']), + # icon + (os.path.join('share', 'pixmaps'), ['data/bpython.png']) ] data_files.extend(man_pages) @@ -224,7 +226,6 @@ def initialize_options(self): packages = packages, data_files = data_files, package_data = { - 'bpython': ['logo.png'], 'bpython.translations': mo_files, 'bpython.test': ['test.config', 'test.theme'] }, From 6d2574c69bd59f95e674ee45622c5454a0a29ba8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Jan 2015 00:08:57 +0100 Subject: [PATCH 0191/1650] Split tests and doc build Signed-off-by: Sebastian Ramacher Fix syntax errors Signed-off-by: Sebastian Ramacher --- .travis.yml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0759ff2d0..1208f2797 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,20 +6,24 @@ python: - "3.3" - "3.4" +env: + - RUN=nosetests + - RUN=build_sphinx + install: - pip install setuptools # core dependencies - - pip install pygments requests + - if [[ $RUN == nosetests ]]; then pip install pygments requests; fi # curtsies specific dependencies - - pip install 'curtsies >=0.1.15,<0.2.0' greenlet + - if [[ $RUN == nosetests ]]; then pip install 'curtsies >=0.1.15,<0.2.0' greenlet; fi # translation specific dependencies - - pip install babel + - if [[ $RUN == nosetests ]]; then pip install babel; fi # documentation specific dependencies - - pip install sphinx - - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi - - python setup.py install + - if [[ $RUN == build_sphinx ]]; then pip install sphinx; fi + - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]] && [[ $RUN == nosetests ]]; then pip install unittest2; fi + - if [[ $RUN == nosetests ]]; then python setup.py install; fi script: - - python setup.py build_sphinx - - cd build/lib/ - - nosetests bpython/test + - if [[ $RUN == build_sphinx ]]; then python setup.py build_sphinx; fi + - if [[ $RUN == build_sphinx ]]; then python setup.py build_sphinx_man; fi + - if [[ $RUN == nosetests ]]; then cd build/lib/ && nosetests bpython/test; fi From aea1a250bade7446b59eb54a6b2319e9c9354384 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 6 Jan 2015 21:09:49 -0500 Subject: [PATCH 0192/1650] completion chooser abstracted, tested also tests for cumulative completion and filename completion --- bpython/autocomplete.py | 137 ++++++++++++++++-------------- bpython/repl.py | 44 +++++----- bpython/test/test_autocomplete.py | 108 +++++++++++++++++++++++ 3 files changed, 207 insertions(+), 82 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 000b9fcdf..7971f146a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -29,6 +29,7 @@ import re import os from glob import glob +from functools import partial from bpython import inspection from bpython import importcompletion from bpython._py3compat import py3 @@ -50,7 +51,7 @@ MAGIC_METHODS = ["__%s__" % s for s in [ "init", "repr", "str", "lt", "le", "eq", "ne", "gt", "ge", "cmp", "hash", - "nonzero", "unicode", "getattr", "setattr", "get", "set","call", "len", + "nonzero", "unicode", "getattr", "setattr", "get", "set", "call", "len", "getitem", "setitem", "iter", "reversed", "contains", "add", "sub", "mul", "floordiv", "mod", "divmod", "pow", "lshift", "rshift", "and", "xor", "or", "div", "truediv", "neg", "pos", "abs", "invert", "complex", "int", "float", @@ -60,15 +61,14 @@ def after_last_dot(name): return name.rstrip('.').rsplit('.')[-1] -def get_completer(cursor_offset, current_line, locals_, argspec, current_block, - mode, complete_magic_methods): - """Returns a list of matches and a class for what kind of completion is happening +def get_completer(completers, cursor_offset, line, **kwargs): + """Returns a list of matches and an applicable completer - If no completion type is relevant, returns None, None + If no matches available, returns a tuple of an empty list and None - Params: + kwargs (all required): cursor_offset is the current cursor column - current_line is a string of the current line + line is a string of the current line locals_ is a dictionary of the environment argspec is an inspect.ArgSpec instance for the current function where the cursor is @@ -79,53 +79,42 @@ def get_completer(cursor_offset, current_line, locals_, argspec, current_block, double underscore methods like __len__ in method signatures """ - kwargs = {'locals_':locals_, 'argspec':argspec, 'current_block':current_block, - 'mode':mode, 'complete_magic_methods':complete_magic_methods} - - # mutually exclusive if matches: If one of these returns [], try the next one - for completer in [DictKeyCompletion]: - matches = completer.matches(cursor_offset, current_line, **kwargs) - if matches: - return sorted(set(matches)), completer - - # mutually exclusive matchers: if one returns [], don't go on - for completer in [StringLiteralAttrCompletion, ImportCompletion, - FilenameCompletion, MagicMethodCompletion, GlobalCompletion]: - matches = completer.matches(cursor_offset, current_line, **kwargs) + for completer in completers: + matches = completer.matches(cursor_offset, line, **kwargs) if matches is not None: - return sorted(set(matches)), completer - - matches = AttrCompletion.matches(cursor_offset, current_line, **kwargs) - - # cumulative completions - try them all - # They all use current_word replacement and formatting - current_word_matches = [] - for completer in [AttrCompletion, ParameterNameCompletion]: - matches = completer.matches(cursor_offset, current_line, **kwargs) - if matches is not None: - current_word_matches.extend(matches) - - if len(current_word_matches) == 0: - return None, None - return sorted(set(current_word_matches)), AttrCompletion + return matches, (completer if matches else None) + return [], None + +def get_completer_bpython(**kwargs): + """""" + return get_completer([DictKeyCompletion, + StringLiteralAttrCompletion, + ImportCompletion, + FilenameCompletion, + MagicMethodCompletion, + GlobalCompletion, + CumulativeCompleter([AttrCompletion, ParameterNameCompletion])], + **kwargs) class BaseCompletionType(object): """Describes different completion types""" + @classmethod def matches(cls, cursor_offset, line, **kwargs): """Returns a list of possible matches given a line and cursor, or None if this completion type isn't applicable. ie, import completion doesn't make sense if there cursor isn't after - an import or from statement + an import or from statement, so it ought to return None. Completion types are used to: - * `locate(cur, line)` their target word to replace given a line and cursor + * `locate(cur, line)` their initial target word to replace given a line and cursor * find `matches(cur, line)` that might replace that word * `format(match)` matches to be displayed to the user * determine whether suggestions should be `shown_before_tab` * `substitute(cur, line, match)` in a match for what's found with `target` """ raise NotImplementedError + @classmethod def locate(cls, cursor_offset, line): """Returns a start, stop, and word given a line and cursor, or None if no target for this type of completion is found under the cursor""" @@ -134,25 +123,58 @@ def locate(cls, cursor_offset, line): def format(cls, word): return word shown_before_tab = True # whether suggestions should be shown before the - # user hits tab, or only once that has happened + # user hits tab, or only once that has happened def substitute(cls, cursor_offset, line, match): """Returns a cursor offset and line with match swapped in""" start, end, word = cls.locate(cursor_offset, line) result = start + len(match), line[:start] + match + line[end:] return result +class CumulativeCompleter(object): + """Returns combined matches from several completers""" + def __init__(self, completers): + if not completers: + raise ValueError("CumulativeCompleter requires at least one completer") + self._completers = completers + self.shown_before_tab = True + + @property + def locate(self): + return self._completers[0].locate if self._completers else lambda *args: None + + @property + def format(self): + return self._completers[0].format if self._completers else lambda s: s + + def matches(self, cursor_offset, line, locals_, argspec, current_block, complete_magic_methods): + all_matches = [] + for completer in self._completers: + # these have to be explicitely listed to deal with the different + # signatures of various matches() methods of completers + matches = completer.matches(cursor_offset=cursor_offset, + line=line, + locals_=locals_, + argspec=argspec, + current_block=current_block, + complete_magic_methods=complete_magic_methods) + if matches is not None: + all_matches.extend(matches) + + return sorted(set(all_matches)) + + class ImportCompletion(BaseCompletionType): @classmethod - def matches(cls, cursor_offset, current_line, **kwargs): - return importcompletion.complete(cursor_offset, current_line) + def matches(cls, cursor_offset, line, **kwargs): + return importcompletion.complete(cursor_offset, line) locate = staticmethod(lineparts.current_word) format = staticmethod(after_last_dot) class FilenameCompletion(BaseCompletionType): shown_before_tab = False @classmethod - def matches(cls, cursor_offset, current_line, **kwargs): - cs = lineparts.current_string(cursor_offset, current_line) + def matches(cls, cursor_offset, line, **kwargs): + cs = lineparts.current_string(cursor_offset, line) if cs is None: return None start, end, text = cs @@ -178,7 +200,7 @@ def format(cls, filename): class AttrCompletion(BaseCompletionType): @classmethod - def matches(cls, cursor_offset, line, locals_, mode, **kwargs): + def matches(cls, cursor_offset, line, locals_, **kwargs): r = cls.locate(cursor_offset, line) if r is None: return None @@ -195,7 +217,7 @@ def matches(cls, cursor_offset, line, locals_, mode, **kwargs): break methodtext = text[-i:] matches = [''.join([text[:-i], m]) for m in - attr_matches(methodtext, locals_, mode)] + attr_matches(methodtext, locals_)] #TODO add open paren for methods via _callable_prefix (or decide not to) # unless the first character is a _ filter out all attributes starting with a _ @@ -242,7 +264,7 @@ def matches(cls, cursor_offset, line, current_block, **kwargs): class GlobalCompletion(BaseCompletionType): @classmethod - def matches(cls, cursor_offset, line, locals_, mode, **kwargs): + def matches(cls, cursor_offset, line, locals_, **kwargs): """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace that match. @@ -256,11 +278,11 @@ def matches(cls, cursor_offset, line, locals_, mode, **kwargs): n = len(text) import keyword for word in keyword.kwlist: - if method_match(word, n, text, mode): + if method_match(word, n, text): hash[word] = 1 for nspace in [__builtin__.__dict__, locals_]: for word, val in nspace.items(): - if method_match(word, len(text), text, mode) and word != "__builtins__": + if method_match(word, len(text), text) and word != "__builtins__": hash[_callable_postfix(val, word)] = 1 matches = hash.keys() matches.sort() @@ -315,7 +337,7 @@ def safe_eval(expr, namespace): raise EvaluationError -def attr_matches(text, namespace, autocomplete_mode): +def attr_matches(text, namespace): """Taken from rlcompleter.py and bent to my will. """ @@ -336,10 +358,10 @@ def attr_matches(text, namespace, autocomplete_mode): except EvaluationError: return [] with inspection.AttrCleaner(obj): - matches = attr_lookup(obj, expr, attr, autocomplete_mode) + matches = attr_lookup(obj, expr, attr) return matches -def attr_lookup(obj, expr, attr, autocomplete_mode): +def attr_lookup(obj, expr, attr): """Second half of original attr_matches method factored out so it can be wrapped in a safe try/finally block in case anything bad happens to restore the original __getattribute__ method.""" @@ -356,7 +378,7 @@ def attr_lookup(obj, expr, attr, autocomplete_mode): matches = [] n = len(attr) for word in words: - if method_match(word, n, attr, autocomplete_mode) and word != "__builtins__": + if method_match(word, n, attr) and word != "__builtins__": matches.append("%s.%s" % (expr, word)) return matches @@ -367,14 +389,5 @@ def _callable_postfix(value, word): word += '(' return word -#TODO use method_match everywhere instead of startswith to implement other completion modes -# will also need to rewrite checking mode so cseq replace doesn't happen in frontends -def method_match(word, size, text, autocomplete_mode): - if autocomplete_mode == SIMPLE: - return word[:size] == text - elif autocomplete_mode == SUBSTRING: - s = r'.*%s.*' % text - return re.search(s, word) - else: - s = r'.*%s.*' % '.*'.join(list(text)) - return re.search(s, word) +def method_match(word, size, text): + return word[:size] == text diff --git a/bpython/repl.py b/bpython/repl.py index 81900aec9..c1c51769b 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -628,6 +628,15 @@ def set_docstring(self): if not self.docstring: self.docstring = None + # What complete() does: + # Should we show the completion box? (are there matches, or is there a docstring to show?) + # Some completions should always be shown, other only if tab=True + # set the current docstring to the "current function's" docstring + # Populate the matches_iter object with new matches from the current state + # if none, clear the matches iterator + # If exactly one match that is equal to current line, clear matches + # If example one match and tab=True, then choose that and clear matches + def complete(self, tab=False): """Construct a full list of possible completions and display them in a window. Also check if there's an available argspec @@ -643,19 +652,16 @@ def complete(self, tab=False): self.set_docstring() - matches, completer = autocomplete.get_completer( - self.cursor_offset, - self.current_line, - self.interp.locals, - self.argspec, - '\n'.join(self.buffer + [self.current_line]), - self.config.autocomplete_mode if hasattr(self.config, 'autocomplete_mode') else autocomplete.SIMPLE, - self.config.complete_magic_methods) + matches, completer = autocomplete.get_completer_bpython( + cursor_offset=self.cursor_offset, + line=self.current_line, + locals_=self.interp.locals, + argspec=self.argspec, + current_block='\n'.join(self.buffer + [self.current_line]), + complete_magic_methods=self.config.complete_magic_methods) #TODO implement completer.shown_before_tab == False (filenames shouldn't fill screen) - if (matches is None # no completion is relevant - or len(matches) == 0): # a target for completion was found - # but no matches were found + if len(matches) == 0: self.matches_iter.clear() return bool(self.argspec) @@ -663,15 +669,13 @@ def complete(self, tab=False): self.current_line, matches, completer) if len(matches) == 1: - self.matches_iter.next() - if tab: # if this complete is being run for a tab key press, tab() to do the swap - - self.cursor_offset, self.current_line = self.matches_iter.substitute_cseq() - return Repl.complete(self) - elif self.matches_iter.current_word == matches[0]: - self.matches_iter.clear() - return False - return completer.shown_before_tab + if tab: # if this complete is being run for a tab key press, substitute common sequence + self._cursor_offset, self._current_line = self.matches_iter.substitute_cseq() + return Repl.complete(self) + elif self.matches_iter.current_word == matches[0]: + self.matches_iter.clear() + return False + return completer.shown_before_tab else: assert len(matches) > 1 diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 1f7d91b78..3b07994e8 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -1,5 +1,6 @@ from bpython import autocomplete +import mock import unittest try: from unittest import skip @@ -31,3 +32,110 @@ def test_filename(self): def test_attribute(self): self.assertEqual(autocomplete.after_last_dot('abc.edf'), 'edf') + +def completer(matches): + mock_completer = autocomplete.BaseCompletionType() + mock_completer.matches = mock.Mock(return_value=matches) + return mock_completer + +class TestGetCompleter(unittest.TestCase): + + def test_no_completers(self): + self.assertTupleEqual(autocomplete.get_completer([], 0, ''), ([], None)) + + def test_one_completer_without_matches_returns_empty_list_and_none(self): + a = completer([]) + self.assertTupleEqual(autocomplete.get_completer([a], 0, ''), ([], None)) + + def test_one_completer_returns_matches_and_completer(self): + a = completer(['a']) + self.assertTupleEqual(autocomplete.get_completer([a], 0, ''), (['a'], a)) + + def test_two_completers_with_matches_returns_first_matches(self): + a = completer(['a']) + b = completer(['b']) + self.assertEqual(autocomplete.get_completer([a, b], 0, ''), (['a'], a)) + + def test_first_non_none_completer_matches_are_returned(self): + a = completer([]) + b = completer(['a']) + self.assertEqual(autocomplete.get_completer([a, b], 0, ''), ([], None)) + + def test_only_completer_returns_None(self): + a = completer(None) + self.assertEqual(autocomplete.get_completer([a], 0, ''), ([], None)) + + def test_first_completer_returns_None(self): + a = completer(None) + b = completer(['a']) + self.assertEqual(autocomplete.get_completer([a, b], 0, ''), (['a'], b)) + +class TestCumulativeCompleter(unittest.TestCase): + + def completer(self, matches, ): + mock_completer = autocomplete.BaseCompletionType() + mock_completer.matches = mock.Mock(return_value=matches) + return mock_completer + + def test_no_completers_fails(self): + with self.assertRaises(ValueError): + autocomplete.CumulativeCompleter([]) + + def test_one_empty_completer_returns_empty(self): + a = self.completer([]) + cumulative = autocomplete.CumulativeCompleter([a]) + self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1), []) + + def test_one_none_completer_returns_empty(self): + a = self.completer(None) + cumulative = autocomplete.CumulativeCompleter([a]) + self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1), []) + + def test_two_completers_get_both(self): + a = self.completer(['a']) + b = self.completer(['b']) + cumulative = autocomplete.CumulativeCompleter([a, b]) + self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1), (['a', 'b'])) + + +class TestFilenameCompletion(unittest.TestCase): + + def test_locate_fails_when_not_in_string(self): + self.assertEqual(autocomplete.FilenameCompletion.locate(4, "abcd"), None) + + def test_locate_succeeds_when_in_string(self): + self.assertEqual(autocomplete.FilenameCompletion.locate(4, "a'bc'd"), (2, 4, 'bc')) + + @mock.patch('bpython.autocomplete.glob', new=lambda text: []) + def test_match_returns_none_if_not_in_string(self): + self.assertEqual(autocomplete.FilenameCompletion.matches(2, 'abcd'), None) + + @mock.patch('bpython.autocomplete.glob', new=lambda text: []) + def test_match_returns_empty_list_when_no_files(self): + self.assertEqual(autocomplete.FilenameCompletion.matches(2, '"a'), []) + + @mock.patch('bpython.autocomplete.glob', new=lambda text: ['abcde', 'aaaaa']) + @mock.patch('os.path.expanduser', new=lambda text: text) + @mock.patch('os.path.isdir', new=lambda text: False) + @mock.patch('os.path.sep', new='/') + def test_match_returns_files_when_files_exist(self): + self.assertEqual(autocomplete.FilenameCompletion.matches(2, '"x'), ['abcde', 'aaaaa']) + + @mock.patch('bpython.autocomplete.glob', new=lambda text: ['abcde', 'aaaaa']) + @mock.patch('os.path.expanduser', new=lambda text: text) + @mock.patch('os.path.isdir', new=lambda text: True) + @mock.patch('os.path.sep', new='/') + def test_match_returns_dirs_when_dirs_exist(self): + self.assertEqual(autocomplete.FilenameCompletion.matches(2, '"x'), ['abcde/', 'aaaaa/']) + + @mock.patch('bpython.autocomplete.glob', new=lambda text: ['/expand/ed/abcde', '/expand/ed/aaaaa']) + @mock.patch('os.path.expanduser', new=lambda text: text.replace('~', '/expand/ed')) + @mock.patch('os.path.isdir', new=lambda text: False) + @mock.patch('os.path.sep', new='/') + def test_tilde_stays_pretty(self): + self.assertEqual(autocomplete.FilenameCompletion.matches(4, '"~/a'), ['~/abcde', '~/aaaaa']) + + @mock.patch('os.path.sep', new='/') + def test_formatting_takes_just_last_part(self): + self.assertEqual(autocomplete.FilenameCompletion.format('/hello/there/'), 'there/') + self.assertEqual(autocomplete.FilenameCompletion.format('/hello/there'), 'there') From 69eebf8f15ad4138dc33b8dde1d7929ac222994b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 15 Jan 2015 02:15:55 -0500 Subject: [PATCH 0193/1650] improve ctrl-Z suspend behavior don't rerender all history automatic refresh on resume --- bpython/curtsies.py | 4 +++- bpython/curtsiesfrontend/repl.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 7d562a41c..f243eaff5 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -79,7 +79,8 @@ def mainloop(config, locals_, banner, interp=None, paste=None, interactive=True) request_refresh = input_generator.event_trigger(bpythonevents.RefreshRequestEvent) schedule_refresh = input_generator.scheduled_event_trigger(bpythonevents.ScheduledRefreshRequestEvent) - request_reload = input_generator.threadsafe_event_trigger(bpythonevents.ReloadEvent) + request_reload = input_generator.threadsafe_event_trigger(bpythonevents.ReloadEvent) + interrupting_refresh = input_generator.threadsafe_event_trigger(lambda: None) def on_suspend(): window.__exit__(None, None, None) @@ -88,6 +89,7 @@ def on_suspend(): def after_suspend(): input_generator.__enter__() window.__enter__() + interrupting_refresh() global repl # global for easy introspection `from bpython.curtsies import repl` with Repl(config=config, diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index eb250d009..0f7745ed3 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -399,6 +399,7 @@ def sigwinch_handler(self, signum, frame): logger.info('decreasing scroll offset by %d to %d', cursor_dy, self.scroll_offset) def sigtstp_handler(self, signum, frame): + self.scroll_offset = len(self.lines_for_display) self.__exit__() self.on_suspend() os.kill(os.getpid(), signal.SIGTSTP) From 4f8e4a81b1a1484cd23e147e42c9be395df3653b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 15 Jan 2015 14:01:36 -0500 Subject: [PATCH 0194/1650] fix python 3 install I assume this was working in the tests because an earlier OSError was caught --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index d83f3ee36..83fc6677a 100755 --- a/setup.py +++ b/setup.py @@ -42,6 +42,8 @@ proc = subprocess.Popen(['git', 'describe', '--tags'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = proc.communicate()[0].rstrip() + if sys.version_info[0] > 2: + stdout = stdout.decode('ascii') if proc.returncode == 0: version_split = stdout.split('-') From c6d7d857d9b5aedc2321a974dccab7aa1286ee41 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Jan 2015 00:24:13 +0100 Subject: [PATCH 0195/1650] We do not need sudo Signed-off-by: Sebastian Ramacher --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1208f2797..5ff1d0035 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python +sudo: false python: - "2.6" From 3a86eaee6f0375a301d79ba8dfdf98ee535a1450 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 15 Jan 2015 21:19:43 +0100 Subject: [PATCH 0196/1650] Do not throw if PYTHONSTARTUPFILE does not exists (fixes #438) Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 0f7745ed3..d66fc7eec 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -420,8 +420,6 @@ def startup(self): self.interp.runsource(f.read(), filename, 'exec') else: self.interp.runsource(f.read(), filename, 'exec') - else: - raise IOError("Python startup file (PYTHONSTARTUP) not found at %s" % filename) def clean_up_current_line_for_exit(self): """Called when trying to exit to prep for final paint""" From 61d7ba886fdbdd5dbe1671e4d2acaa6ca02b8dec Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 15 Jan 2015 20:04:18 -0500 Subject: [PATCH 0197/1650] improve help message, fix spelling --- bpython/curtsiesfrontend/events.py | 4 ++-- bpython/curtsiesfrontend/repl.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index 1b45c2956..299326135 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -1,4 +1,4 @@ -"""Non-keybaord events used in bpython curtsies REPL""" +"""Non-keyboard events used in bpython curtsies REPL""" import time import curtsies.events @@ -25,7 +25,7 @@ def __repr__(self): class ScheduledRefreshRequestEvent(curtsies.events.ScheduledEvent): """Request to refresh the REPL display at some point in the future - Used to schedule the dissapearance of status bar message that only show + Used to schedule the disappearance of status bar message that only shows for a few seconds""" def __init__(self, when, who='?'): self.who = who diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d66fc7eec..f9e331c2b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -73,6 +73,7 @@ Use a config file at {config_file_location} to customize keys and behavior of bpython. You can customize which pastebin helper to use and which external editor to use. See {example_config_url} for an example config file. +Press {config.edit_config_key} to edit this config file. """ class FakeStdin(object): @@ -1385,6 +1386,8 @@ def key_help_text(self): pairs = [] pairs.append(['complete history suggestion', 'right arrow at end of line']) pairs.append(['previous match with current line', 'up arrow']) + pairs.append(['reverse incremental search', 'M-r']) + pairs.append(['incremental search', 'M-s']) for functionality, key in [(attr[:-4].replace('_', ' '), getattr(self.config, attr)) for attr in self.config.__dict__ if attr.endswith('key')]: From 09f905f40b51e81e55e8ed2035ef0ffb4bf27b4c Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 15 Jan 2015 20:04:30 -0500 Subject: [PATCH 0198/1650] fix pasting multiple lines --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f9e331c2b..7acace754 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -690,7 +690,7 @@ def process_simple_keypress(self, e): self.on_enter() while self.fake_refresh_requested: self.fake_refresh_requested = False - self.process_event(events.RefreshRequestEvent()) + self.process_event(bpythonevents.RefreshRequestEvent()) elif isinstance(e, events.Event): pass # ignore events elif e == '': From 26985317269a215d9c03af5e637fefc2ab5d3623 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 16 Jan 2015 18:48:46 -0500 Subject: [PATCH 0199/1650] fix a redrawing error there should be a way to set both at once - this is another case of changing cursor offset and then line causing problems or vice versa. --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 7acace754..1d2b71fdc 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1277,8 +1277,8 @@ def take_back_buffer_line(self): self.buffer.pop() if not self.buffer: + self._set_cursor_offset(0, update_completion=False) self.current_line = '' - self.cursor_offset = 0 else: line = self.buffer[-1] indent = self.predicted_indent(line) From 3a65ba9cc04bc2d6b53c2da9252336476b7aec3d Mon Sep 17 00:00:00 2001 From: Keyan Pishdadian Date: Fri, 16 Jan 2015 22:58:40 -0500 Subject: [PATCH 0200/1650] Refactor filewatching and add tests. Change API of filewatch.py, update curtsies repl to reflect changes. --- bpython/curtsiesfrontend/filewatch.py | 40 ++++++++++++++++----------- bpython/curtsiesfrontend/repl.py | 10 ++----- bpython/test/test_filewatch.py | 30 ++++++++++++++++++++ 3 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 bpython/test/test_filewatch.py diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 57955cbc5..6529f2e1b 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -19,8 +19,9 @@ def __init__(self, paths, on_change): self.observer = Observer() self.old_dirs = defaultdict(set) self.started = False + self.activated = False for path in paths: - self.add_module(path) + self._add_module(path) def reset(self): self.dirs = defaultdict(set) @@ -28,8 +29,10 @@ def reset(self): self.old_dirs = defaultdict(set) self.observer.unschedule_all() - def add_module(self, path): - """Add a python module to track changes to""" + def _add_module(self, path): + """Add a python module to track changes to + + Can""" path = os.path.abspath(path) for suff in importcompletion.SUFFIXES: if path.endswith(suff): @@ -40,24 +43,36 @@ def add_module(self, path): self.observer.schedule(self, dirname, recursive=False) self.dirs[os.path.dirname(path)].add(path) - def add_module_later(self, path): + def _add_module_later(self, path): self.modules_to_add_later.append(path) + + def track_module(self, path): + """ + Begins tracking this if activated, or remembers to track later. + """ + if self.activated: + self._add_module(path) + else: + self._add_module_later(path) def activate(self): + if self.activated: + raise ValueError("%r is already activated." % (self,)) if not self.started: self.started = True self.observer.start() - self.dirs = self.old_dirs for dirname in self.dirs: self.observer.schedule(self, dirname, recursive=False) for module in self.modules_to_add_later: - self.add_module(module) + self._add_module(module) del self.modules_to_add_later[:] + self.activated = True def deactivate(self): + if not self.activated: + raise ValueError("%r is not activated." % (self,)) self.observer.unschedule_all() - self.old_dirs = self.dirs - self.dirs = defaultdict(set) + self.activated = False def on_any_event(self, event): dirpath = os.path.dirname(event.src_path) @@ -66,12 +81,5 @@ def on_any_event(self, event): self.on_change(files_modified=[event.src_path]) if __name__ == '__main__': - m = ModuleChangedEventHandler([]) - m.add_module('./wdtest.py') - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - m.observer.stop() - m.observer.join() + pass diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 1d2b71fdc..3dd9fb0ad 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -366,18 +366,12 @@ def new_import(name, globals={}, locals={}, fromlist=[], level=-1): except: if name in old_module_locations: loc = old_module_locations[name] - if self.watching_files: - self.watcher.add_module(loc) - else: - self.watcher.add_module_later(loc) + self.watcher.track_module(loc) raise else: if hasattr(m, "__file__"): old_module_locations[name] = m.__file__ - if self.watching_files: - self.watcher.add_module(m.__file__) - else: - self.watcher.add_module_later(m.__file__) + self.watcher.track_module(m.__file__) return m __builtins__['__import__'] = new_import diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py new file mode 100644 index 000000000..242a5a153 --- /dev/null +++ b/bpython/test/test_filewatch.py @@ -0,0 +1,30 @@ +import mock +import os + +try: + import unittest2 as unittest +except ImportError: + import unittest + +from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler + +class TestModuleChangeEventHandler(unittest.TestCase): + + def setUp(self): + self.module = ModuleChangedEventHandler([], 1) + self.module.observer = mock.Mock() + + def test_create_module_handler(self): + self.assertIsInstance(self.module, ModuleChangedEventHandler) + + def test_add_module(self): + self.module._add_module('something/test.py') + self.assertIn(os.path.abspath('something/test'), + self.module.dirs[os.path.abspath('something')]) + + def test_activate_throws_error_when_already_activated(self): + self.module.activated = True + with self.assertRaises(ValueError): + self.module.activate() + + \ No newline at end of file From 20c6b29f2814a3d21cf56e32df8c3c914dc50434 Mon Sep 17 00:00:00 2001 From: Keyan Pishdadian Date: Fri, 16 Jan 2015 23:24:41 -0500 Subject: [PATCH 0201/1650] Add watchdog to travis config for filewatch tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5ff1d0035..60ad4719e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ install: # core dependencies - if [[ $RUN == nosetests ]]; then pip install pygments requests; fi # curtsies specific dependencies - - if [[ $RUN == nosetests ]]; then pip install 'curtsies >=0.1.15,<0.2.0' greenlet; fi + - if [[ $RUN == nosetests ]]; then pip install 'curtsies >=0.1.15,<0.2.0' greenlet watchdog; fi # translation specific dependencies - if [[ $RUN == nosetests ]]; then pip install babel; fi # documentation specific dependencies From a65da4eec06e6d42786373d8f9ae2a4d4376da52 Mon Sep 17 00:00:00 2001 From: Keyan Pishdadian Date: Fri, 16 Jan 2015 23:43:17 -0500 Subject: [PATCH 0202/1650] Add deactivate message when turning off auto-reload --- bpython/curtsiesfrontend/repl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 1d2b71fdc..b9ae2b530 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -737,13 +737,15 @@ def clear_modules_and_reevaluate(self): def toggle_file_watch(self): if self.watcher: - msg = "Auto-reloading active, watching for file changes..." if self.watching_files: + msg = "Auto-reloading deactivated" + self.status_bar.message(msg) self.watcher.deactivate() self.watching_files = False else: - self.watching_files = True + msg = "Auto-reloading active, watching for file changes..." self.status_bar.message(msg) + self.watching_files = True self.watcher.activate() else: self.status_bar.message('Autoreloading not available because watchdog not installed') From 90e2bbea53b7de2801db90f3b0a2df1d99c3821d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 17 Jan 2015 14:55:14 +0100 Subject: [PATCH 0203/1650] Skip watchdog tests if watchdog is not installed Signed-off-by: Sebastian Ramacher --- bpython/test/test_filewatch.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index 242a5a153..e568d2f9a 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -6,25 +6,29 @@ except ImportError: import unittest -from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler +try: + from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler + has_watchdog = True +except ImportError: + has_watchdog = False +@unittest.skipIf(not has_watchdog, "watchdog not available") class TestModuleChangeEventHandler(unittest.TestCase): - + def setUp(self): self.module = ModuleChangedEventHandler([], 1) self.module.observer = mock.Mock() - + def test_create_module_handler(self): self.assertIsInstance(self.module, ModuleChangedEventHandler) - + def test_add_module(self): self.module._add_module('something/test.py') - self.assertIn(os.path.abspath('something/test'), + self.assertIn(os.path.abspath('something/test'), self.module.dirs[os.path.abspath('something')]) - + def test_activate_throws_error_when_already_activated(self): self.module.activated = True with self.assertRaises(ValueError): self.module.activate() - - \ No newline at end of file + From 9162de5364853a237924ab1003b704cffd78d253 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 17 Jan 2015 14:57:17 +0100 Subject: [PATCH 0204/1650] Really test if watchdog is available Signed-off-by: Sebastian Ramacher --- bpython/test/test_filewatch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index e568d2f9a..192768894 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -7,6 +7,7 @@ import unittest try: + import watchdog from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler has_watchdog = True except ImportError: From bed1c2707b1b2c2b1738cc20815deb30a4eed5ab Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 17 Jan 2015 15:08:37 +0100 Subject: [PATCH 0205/1650] Remove another empty test Signed-off-by: Sebastian Ramacher --- bpython/test/test_formatter.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 bpython/test/test_formatter.py diff --git a/bpython/test/test_formatter.py b/bpython/test/test_formatter.py deleted file mode 100644 index e69de29bb..000000000 From 6ead79c5e55aa846042729e501bc627b08c048ea Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 17 Jan 2015 15:41:27 +0100 Subject: [PATCH 0206/1650] Use nose.plugins.attrib to mark some tests as slow Slow tests can be skipped with nosetests bpython/test -A "speed != 'slow'" Signed-off-by: Sebastian Ramacher --- bpython/test/test_crashers.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index a28fdf134..2ab110148 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -19,6 +19,18 @@ except ImportError: reactor = None +try: + import urwid + have_urwid = True +except ImportError: + have_urwid = False + +try: + from nose.plugins.attrib import attr +except ImportError: + def attr(func, *args, **kwargs): + return func + TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") def set_win_size(fd, rows, columns): @@ -103,13 +115,15 @@ def spam(a, (b, c)): def check_no_traceback(self, data): self.assertNotIn("Traceback", data) -if reactor is not None: - class CursesCrashersTest(TrialTestCase, CrashersTest): - backend = "cli" +@unittest.skipIf(reactor is None, "twisted is not available") +class CursesCrashersTest(TrialTestCase, CrashersTest): + backend = "cli" - @unittest.skip("take 6 seconds, and Simon says we can skip them") - class UrwidCrashersTest(TrialTestCase, CrashersTest): - backend = "urwid" +@attr(speed='slow') +@unittest.skipIf(not have_urwid, "urwid is not available") +@unittest.skipIf(reactor is None, "twisted is not available") +class UrwidCrashersTest(TrialTestCase, CrashersTest): + backend = "urwid" if __name__ == "__main__": unittest.main() From 2af8ad85f11f22cf57cd6597043a17e265d0b87e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 17 Jan 2015 15:44:59 +0100 Subject: [PATCH 0207/1650] Define TrialTestCase Signed-off-by: Sebastian Ramacher --- bpython/test/test_crashers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 2ab110148..afb890c11 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -17,6 +17,8 @@ from twisted.internet.protocol import ProcessProtocol from twisted.trial.unittest import TestCase as TrialTestCase except ImportError: + class TrialTestCase(object): + pass reactor = None try: From f48a22829a1a134219b7614650cc4ca3fa42443b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 17 Jan 2015 12:33:44 -0500 Subject: [PATCH 0208/1650] require newer curtsies for Python 3 comparison fix https://github.com/thomasballinger/curtsies/issues/63 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 83fc6677a..ca044ed10 100755 --- a/setup.py +++ b/setup.py @@ -168,7 +168,7 @@ def initialize_options(self): install_requires = [ 'pygments', 'requests', - 'curtsies >=0.1.15, <0.2.0', + 'curtsies >=0.1.16, <0.2.0', 'greenlet' ] From a8bae8ab52c4f27083b59b954cfffc717ab8a456 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 17 Jan 2015 15:55:34 +0100 Subject: [PATCH 0209/1650] Use bpython._py3compat.PythonLexer everywhere Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 2 +- bpython/curtsiesfrontend/repl.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 3728b35fe..264db8526 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -64,7 +64,7 @@ # These are used for syntax highlighting from pygments import format from pygments.formatters import TerminalFormatter -from pygments.lexers import PythonLexer +from bpython._py3compat import PythonLexer from pygments.token import Token from bpython.formatter import BPythonFormatter diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 60e5a3796..c56978725 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -15,7 +15,7 @@ import unicodedata from pygments import format -from pygments.lexers import PythonLexer +from bpython._py3compat import PythonLexer from pygments.formatters import TerminalFormatter from interpreter import Interp From 96778446401fb301c0af823125486d79f2356ea9 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 17 Jan 2015 12:36:55 -0500 Subject: [PATCH 0210/1650] update travis to use require newest released curtsies --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 60ad4719e..1d56edc97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ install: # core dependencies - if [[ $RUN == nosetests ]]; then pip install pygments requests; fi # curtsies specific dependencies - - if [[ $RUN == nosetests ]]; then pip install 'curtsies >=0.1.15,<0.2.0' greenlet watchdog; fi + - if [[ $RUN == nosetests ]]; then pip install 'curtsies >=0.1.16,<0.2.0' greenlet watchdog; fi # translation specific dependencies - if [[ $RUN == nosetests ]]; then pip install babel; fi # documentation specific dependencies From 47d5a49170e4fdf53d7e3f3fb5ff9bef66c090f3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 17 Jan 2015 18:40:21 +0100 Subject: [PATCH 0211/1650] Fix find source with empty input (fixes #443) Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c56978725..c574b7271 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1359,7 +1359,7 @@ def show_source(self): try: source = self.get_source_of_current_name() except SourceNotFound as e: - self.status_bar.message(_(e)) + self.status_bar.message(_(str(e))) else: if self.config.highlight_show_source: source = format(PythonLexer().get_tokens(source), From e542a9b4d7e7d9d0f6e10c1c4d216d042bb0b98a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 17 Jan 2015 18:47:43 +0100 Subject: [PATCH 0212/1650] Move _ to a better place Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 2 +- bpython/repl.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c574b7271..efc4dfb4f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1359,7 +1359,7 @@ def show_source(self): try: source = self.get_source_of_current_name() except SourceNotFound as e: - self.status_bar.message(_(str(e))) + self.status_bar.message(str(e)) else: if self.config.highlight_show_source: source = format(PythonLexer().get_tokens(source), diff --git a/bpython/repl.py b/bpython/repl.py index 860928f33..0ad61f560 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -468,19 +468,19 @@ def get_source_of_current_name(self): if obj is None: line = self.current_line if not line.strip(): - raise SourceNotFound("Nothing to get source of") + raise SourceNotFound(_("Nothing to get source of")) if inspection.is_eval_safe_name(line): obj = self.get_object(line) return inspect.getsource(obj) except (AttributeError, NameError) as e: - msg = "Cannot get source: " + str(e) + msg = _("Cannot get source: %s") % (str(e), ) except IOError as e: msg = str(e) except TypeError as e: if "built-in" in str(e): - msg = "Cannot access source of %r" % (obj, ) + msg = _("Cannot access source of %r") % (obj, ) else: - msg = "No source code found for %s" % (self.current_line, ) + msg = _("No source code found for %s") % (self.current_line, ) raise SourceNotFound(msg) def set_docstring(self): From 0740a7102716e09ebad034cba4ac5fa101127d9d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 17 Jan 2015 22:15:42 +0100 Subject: [PATCH 0213/1650] Update translations Signed-off-by: Sebastian Ramacher --- bpython/translations/bpython.pot | 146 ++++++++---- .../translations/de/LC_MESSAGES/bpython.po | 205 ++++++++++------- .../translations/es_ES/LC_MESSAGES/bpython.po | 196 ++++++++++------ .../translations/it_IT/LC_MESSAGES/bpython.po | 207 +++++++++++------ .../translations/nl_NL/LC_MESSAGES/bpython.po | 214 +++++++++++------- 5 files changed, 628 insertions(+), 340 deletions(-) diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 78a4ecad6..46e615d9d 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -1,14 +1,14 @@ # Translations template for bpython. -# Copyright (C) 2014 ORGANIZATION +# Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the bpython project. -# FIRST AUTHOR , 2014. +# FIRST AUTHOR , 2015. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython mercurial\n" +"Project-Id-Version: bpython 0.13-381\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2014-05-13 10:50+0200\n" +"POT-Creation-Date: 2015-01-17 18:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,135 +17,191 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: bpython/args.py:53 +#: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:63 +#: bpython/args.py:67 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:65 +#: bpython/args.py:69 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:68 +#: bpython/args.py:72 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:70 +#: bpython/args.py:74 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:325 bpython/urwid.py:553 +#: bpython/cli.py:321 bpython/urwid.py:555 msgid "y" msgstr "" -#: bpython/cli.py:325 bpython/urwid.py:553 +#: bpython/cli.py:321 bpython/urwid.py:555 msgid "yes" msgstr "" -#: bpython/cli.py:1001 -msgid "Cannot show source." +#: bpython/cli.py:1696 +msgid "Rewind" msgstr "" -#: bpython/cli.py:1751 bpython/urwid.py:615 -#, python-format -msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " +#: bpython/cli.py:1697 +msgid "Save" +msgstr "" + +#: bpython/cli.py:1698 +msgid "Pastebin" +msgstr "" + +#: bpython/cli.py:1699 +msgid "Pager" msgstr "" -#: bpython/curtsies.py:24 -msgid "log debug messages to bpython-curtsies.log" +#: bpython/cli.py:1700 +msgid "Show Source" msgstr "" -#: bpython/curtsies.py:26 +#: bpython/curtsies.py:31 +msgid "log debug messages to bpython.log" +msgstr "" + +#: bpython/curtsies.py:33 msgid "enter lines of file as though interactively typed" msgstr "" -#: bpython/repl.py:755 -msgid "Pastebin buffer? (y/N) " +#: bpython/history.py:236 +#, python-format +msgid "Error occurded while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:756 -msgid "Pastebin aborted" +#: bpython/repl.py:471 +msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:763 +#: bpython/repl.py:476 #, python-format -msgid "Duplicate pastebin. Previous URL: %s" +msgid "Cannot get source: %s" +msgstr "" + +#: bpython/repl.py:481 +#, python-format +msgid "Cannot access source of %r" +msgstr "" + +#: bpython/repl.py:483 +#, python-format +msgid "No source code found for %s" +msgstr "" + +#: bpython/repl.py:649 +msgid "No clipboard available." +msgstr "" + +#: bpython/repl.py:656 +msgid "Could not copy to clipboard." +msgstr "" + +#: bpython/repl.py:658 +msgid "Copied content to clipboard." +msgstr "" + +#: bpython/repl.py:667 +msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:777 +#: bpython/repl.py:668 +msgid "Pastebin aborted" +msgstr "" + +#: bpython/repl.py:675 #, python-format -msgid "Pastebin error for URL '%s': %s" +msgid "Duplicate pastebin. Previous URL: %s" msgstr "" -#: bpython/repl.py:781 bpython/repl.py:800 +#: bpython/repl.py:694 bpython/repl.py:721 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:786 +#: bpython/repl.py:699 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:795 bpython/repl.py:839 +#: bpython/repl.py:714 #, python-format -msgid "Pastebin URL: %s" +msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:812 +#: bpython/repl.py:733 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:815 +#: bpython/repl.py:736 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:822 +#: bpython/repl.py:743 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:826 +#: bpython/repl.py:747 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:833 +#: bpython/repl.py:754 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/urwid.py:1114 +#: bpython/repl.py:760 +#, python-format +msgid "Pastebin URL: %s" +msgstr "" + +#: bpython/repl.py:946 +msgid "Config file does not exist - create new from default? (y/N)" +msgstr "" + +#: bpython/urwid.py:617 +#, python-format +msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " +msgstr "" + +#: bpython/urwid.py:1126 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1116 +#: bpython/urwid.py:1128 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1119 +#: bpython/urwid.py:1131 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1121 +#: bpython/urwid.py:1133 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1124 +#: bpython/urwid.py:1136 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:175 -msgid "welcome to bpython" +#: bpython/curtsiesfrontend/repl.py:258 +msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:189 +#: bpython/curtsiesfrontend/repl.py:258 #, python-format -msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Editor" +msgid "Press <%s> for help." msgstr "" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 88691c50f..01b8f57ab 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,176 +7,223 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2013-10-10 23:29+0200\n" -"PO-Revision-Date: 2013-10-11 14:51+0200\n" +"POT-Creation-Date: 2015-01-17 18:57+0100\n" +"PO-Revision-Date: 2015-01-17 22:13+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: de \n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" +"Language: de\n" +"X-Generator: Poedit 1.6.10\n" -#: bpython/args.py:53 +#: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back " -"to the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back to " +"the regular Python interpreter." msgstr "" -#: bpython/args.py:63 +#: bpython/args.py:67 msgid "Use CONFIG instead of default config file." msgstr "Verwende CONFIG antatt der standardmäßigen Konfigurationsdatei." -#: bpython/args.py:65 +#: bpython/args.py:69 msgid "Drop to bpython shell after running file instead of exiting." msgstr "Verbleibe in bpython nach dem Ausführen der Datei." -#: bpython/args.py:68 +#: bpython/args.py:72 msgid "Don't flush the output to stdout." msgstr "Gib Ausgabe beim Beenden nicht ernaut auf stdout aus." -#: bpython/args.py:70 +#: bpython/args.py:74 msgid "Print version and exit." msgstr "Zeige Versionsinformationen an und beende." -#: bpython/cli.py:304 bpython/urwid.py:553 +#: bpython/cli.py:321 bpython/urwid.py:555 msgid "y" msgstr "j" -#: bpython/cli.py:304 bpython/urwid.py:553 +#: bpython/cli.py:321 bpython/urwid.py:555 msgid "yes" msgstr "ja" -#: bpython/cli.py:977 bpython/gtk_.py:483 -msgid "Cannot show source." -msgstr "Kann Quellcode nicht anzeigen." +#: bpython/cli.py:1696 +msgid "Rewind" +msgstr "" -#: bpython/cli.py:1677 bpython/urwid.py:615 -#, python-format -msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " +#: bpython/cli.py:1697 +msgid "Save" +msgstr "Speichern" + +#: bpython/cli.py:1698 +msgid "Pastebin" msgstr "" -#: bpython/gtk_.py:90 bpython/gtk_.py:115 -msgid "An error occurred." -msgstr "Ein Fehler ist aufgetreten." +#: bpython/cli.py:1699 +msgid "Pager" +msgstr "" -#: bpython/gtk_.py:97 -msgid "Exception details" -msgstr "Ausnahmedetails" +#: bpython/cli.py:1700 +msgid "Show Source" +msgstr "Quellcode anzeigen" -#: bpython/gtk_.py:149 -msgid "Statusbar" -msgstr "Statusleiste" +#: bpython/curtsies.py:31 +msgid "log debug messages to bpython.log" +msgstr "" -#: bpython/gtk_.py:226 -msgid "tooltip" +#: bpython/curtsies.py:33 +msgid "enter lines of file as though interactively typed" msgstr "" -#: bpython/gtk_.py:295 -msgid "File to save to" +#: bpython/history.py:236 +#, python-format +msgid "Error occurded while writing to file %s (%s)" +msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" + +#: bpython/repl.py:471 +msgid "Nothing to get source of" msgstr "" -#: bpython/gtk_.py:306 -msgid "Python files" -msgstr "Python Dateien" +#: bpython/repl.py:476 +#, python-format +msgid "Cannot get source: %s" +msgstr "Kann Quellcode nicht finden: %s" -#: bpython/gtk_.py:311 -msgid "All files" -msgstr "Alle Dateien" +#: bpython/repl.py:481 +#, python-format +msgid "Cannot access source of %r" +msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/gtk_.py:771 -msgid "gtk-specific options" -msgstr "" +#: bpython/repl.py:483 +#, python-format +msgid "No source code found for %s" +msgstr "Quellcode für %s nicht gefunden" -#: bpython/gtk_.py:772 -msgid "Options specific to bpython's Gtk+ front end" -msgstr "" +#: bpython/repl.py:649 +msgid "No clipboard available." +msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/gtk_.py:774 -msgid "Embed bpython" -msgstr "Bette bpython ein" +#: bpython/repl.py:656 +msgid "Could not copy to clipboard." +msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/gtk_.py:830 -msgid "Pastebin selection" -msgstr "" +#: bpython/repl.py:658 +msgid "Copied content to clipboard." +msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:741 +#: bpython/repl.py:667 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:668 msgid "Pastebin aborted" msgstr "" -#: bpython/repl.py:749 +#: bpython/repl.py:675 #, python-format msgid "Duplicate pastebin. Previous URL: %s" msgstr "" -#: bpython/repl.py:763 -#, python-format -msgid "Pastebin error for URL '%s': %s" -msgstr "" - -#: bpython/repl.py:767 bpython/repl.py:786 +#: bpython/repl.py:694 bpython/repl.py:721 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." -#: bpython/repl.py:772 +#: bpython/repl.py:699 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:781 bpython/repl.py:825 +#: bpython/repl.py:714 #, python-format -msgid "Pastebin URL: %s" +msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:798 +#: bpython/repl.py:733 msgid "Upload failed: Helper program not found." -msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht gefunden " -"werden." +msgstr "" +"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht gefunden werden." -#: bpython/repl.py:801 +#: bpython/repl.py:736 msgid "Upload failed: Helper program could not be run." -msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht ausgeführt " -"werden." +msgstr "" +"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht ausgeführt werden." -#: bpython/repl.py:808 +#: bpython/repl.py:743 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm beendete mit Status %s." -#: bpython/repl.py:812 +#: bpython/repl.py:747 msgid "Upload failed: No output from helper program." -msgstr "Hochladen ist fehlgeschlagen: Keine Ausgabe von Hilfsprogramm " -"vorhanden." +msgstr "" +"Hochladen ist fehlgeschlagen: Keine Ausgabe von Hilfsprogramm vorhanden." -#: bpython/repl.py:819 -msgid "Upload failed: Failed to recognize the helper program's output as an URL." -msgstr "Hochladen ist fehlgeschlagen: Konte Ausgabe von Hilfsprogramm nicht " +#: bpython/repl.py:754 +msgid "" +"Upload failed: Failed to recognize the helper program's output as an URL." +msgstr "" +"Hochladen ist fehlgeschlagen: Konte Ausgabe von Hilfsprogramm nicht " "verarbeiten." -#: bpython/urwid.py:1114 +#: bpython/repl.py:760 +#, python-format +msgid "Pastebin URL: %s" +msgstr "" + +#: bpython/repl.py:946 +msgid "Config file does not exist - create new from default? (y/N)" +msgstr "" + +#: bpython/urwid.py:617 +#, python-format +msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " +msgstr "" + +#: bpython/urwid.py:1126 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1116 +#: bpython/urwid.py:1128 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1119 +#: bpython/urwid.py:1131 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1121 +#: bpython/urwid.py:1133 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1124 +#: bpython/urwid.py:1136 msgid "Port to run an eval server on (forces Twisted)." msgstr "" +#: bpython/curtsiesfrontend/repl.py:258 +msgid "Welcome to bpython!" +msgstr "Willkommen by bpython!" + +#: bpython/curtsiesfrontend/repl.py:258 +#, python-format +msgid "Press <%s> for help." +msgstr "Drücke <%s> für Hilfe." + +#~ msgid "An error occurred." +#~ msgstr "Ein Fehler ist aufgetreten." + +#~ msgid "Exception details" +#~ msgstr "Ausnahmedetails" + +#~ msgid "Statusbar" +#~ msgstr "Statusleiste" + +#~ msgid "Python files" +#~ msgstr "Python Dateien" + +#~ msgid "All files" +#~ msgstr "Alle Dateien" diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index f9e2af3d5..06a82322e 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2013-10-10 23:29+0200\n" +"POT-Creation-Date: 2015-01-17 18:57+0100\n" "PO-Revision-Date: 2013-10-11 14:46+0200\n" "Last-Translator: Claudia Medde\n" "Language-Team: bpython developers\n" @@ -17,164 +17,228 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: bpython/args.py:53 +#: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:63 +#: bpython/args.py:67 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:65 +#: bpython/args.py:69 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:68 +#: bpython/args.py:72 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:70 +#: bpython/args.py:74 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:304 bpython/urwid.py:553 +#: bpython/cli.py:321 bpython/urwid.py:555 msgid "y" msgstr "s" -#: bpython/cli.py:304 bpython/urwid.py:553 +#: bpython/cli.py:321 bpython/urwid.py:555 msgid "yes" msgstr "si" -#: bpython/cli.py:977 bpython/gtk_.py:483 -msgid "Cannot show source." -msgstr "Imposible mostrar el código fuente" +#: bpython/cli.py:1696 +msgid "Rewind" +msgstr "" -#: bpython/cli.py:1677 bpython/urwid.py:615 -#, python-format -msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " +#: bpython/cli.py:1697 +msgid "Save" msgstr "" -" <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra el " -"código fuente" -#: bpython/gtk_.py:90 bpython/gtk_.py:115 -msgid "An error occurred." +#: bpython/cli.py:1698 +msgid "Pastebin" msgstr "" -#: bpython/gtk_.py:97 -msgid "Exception details" +#: bpython/cli.py:1699 +msgid "Pager" msgstr "" -#: bpython/gtk_.py:149 -msgid "Statusbar" -msgstr "Statusbar" +#: bpython/cli.py:1700 +#, fuzzy +msgid "Show Source" +msgstr "Imposible mostrar el código fuente" -#: bpython/gtk_.py:226 -msgid "tooltip" -msgstr "tooltip" +#: bpython/curtsies.py:31 +msgid "log debug messages to bpython.log" +msgstr "" + +#: bpython/curtsies.py:33 +msgid "enter lines of file as though interactively typed" +msgstr "" + +#: bpython/history.py:236 +#, python-format +msgid "Error occurded while writing to file %s (%s)" +msgstr "" -#: bpython/gtk_.py:295 -msgid "File to save to" +#: bpython/repl.py:471 +msgid "Nothing to get source of" msgstr "" -#: bpython/gtk_.py:306 -msgid "Python files" -msgstr "Files Python" +#: bpython/repl.py:476 +#, fuzzy, python-format +msgid "Cannot get source: %s" +msgstr "Imposible mostrar el código fuente" -#: bpython/gtk_.py:311 -msgid "All files" -msgstr "Todos los files" +#: bpython/repl.py:481 +#, fuzzy, python-format +msgid "Cannot access source of %r" +msgstr "Imposible mostrar el código fuente" -#: bpython/gtk_.py:771 -msgid "gtk-specific options" +#: bpython/repl.py:483 +#, python-format +msgid "No source code found for %s" msgstr "" -#: bpython/gtk_.py:772 -msgid "Options specific to bpython's Gtk+ front end" +#: bpython/repl.py:649 +msgid "No clipboard available." msgstr "" -#: bpython/gtk_.py:774 -msgid "Embed bpython" -msgstr "Embed bpython" +#: bpython/repl.py:656 +msgid "Could not copy to clipboard." +msgstr "" -#: bpython/gtk_.py:830 -msgid "Pastebin selection" -msgstr "Pastebin la selección" +#: bpython/repl.py:658 +msgid "Copied content to clipboard." +msgstr "" -#: bpython/repl.py:741 +#: bpython/repl.py:667 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:668 msgid "Pastebin aborted" msgstr "" -#: bpython/repl.py:749 +#: bpython/repl.py:675 #, python-format msgid "Duplicate pastebin. Previous URL: %s" msgstr "" -#: bpython/repl.py:763 -#, python-format -msgid "Pastebin error for URL '%s': %s" -msgstr "" - -#: bpython/repl.py:767 bpython/repl.py:786 +#: bpython/repl.py:694 bpython/repl.py:721 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:772 +#: bpython/repl.py:699 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:781 bpython/repl.py:825 +#: bpython/repl.py:714 #, python-format -msgid "Pastebin URL: %s" +msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:798 +#: bpython/repl.py:733 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:801 +#: bpython/repl.py:736 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:808 +#: bpython/repl.py:743 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:812 +#: bpython/repl.py:747 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:819 +#: bpython/repl.py:754 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/urwid.py:1114 +#: bpython/repl.py:760 +#, python-format +msgid "Pastebin URL: %s" +msgstr "" + +#: bpython/repl.py:946 +msgid "Config file does not exist - create new from default? (y/N)" +msgstr "" + +#: bpython/urwid.py:617 +#, python-format +msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " +msgstr "" +" <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra el " +"código fuente" + +#: bpython/urwid.py:1126 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1116 +#: bpython/urwid.py:1128 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1119 +#: bpython/urwid.py:1131 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1121 +#: bpython/urwid.py:1133 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1124 +#: bpython/urwid.py:1136 msgid "Port to run an eval server on (forces Twisted)." msgstr "" +#: bpython/curtsiesfrontend/repl.py:258 +#, fuzzy +msgid "Welcome to bpython!" +msgstr "Embed bpython" + +#: bpython/curtsiesfrontend/repl.py:258 +#, python-format +msgid "Press <%s> for help." +msgstr "" + +#~ msgid "An error occurred." +#~ msgstr "" + +#~ msgid "Exception details" +#~ msgstr "" + +#~ msgid "Statusbar" +#~ msgstr "Statusbar" + +#~ msgid "tooltip" +#~ msgstr "tooltip" + +#~ msgid "File to save to" +#~ msgstr "" + +#~ msgid "Python files" +#~ msgstr "Files Python" + +#~ msgid "All files" +#~ msgstr "Todos los files" + +#~ msgid "gtk-specific options" +#~ msgstr "" + +#~ msgid "Options specific to bpython's Gtk+ front end" +#~ msgstr "" + +#~ msgid "Pastebin selection" +#~ msgstr "Pastebin la selección" + +#~ msgid "Pastebin error for URL '%s': %s" +#~ msgstr "" + diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index d7cc4ce6d..10bcee5fa 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,172 +7,231 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2013-10-10 23:29+0200\n" -"PO-Revision-Date: 2013-10-11 14:47+0200\n" -"Last-Translator: Michele Orrù\n" +"POT-Creation-Date: 2015-01-17 18:57+0100\n" +"PO-Revision-Date: 2015-01-17 22:15+0100\n" +"Last-Translator: Sebastian Ramacher \n" "Language-Team: Michele Orrù\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" +"Language: it_IT\n" +"X-Generator: Poedit 1.6.10\n" -#: bpython/args.py:53 +#: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back " -"to the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back to " +"the regular Python interpreter." msgstr "" -#: bpython/args.py:63 +#: bpython/args.py:67 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:65 +#: bpython/args.py:69 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:68 +#: bpython/args.py:72 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:70 +#: bpython/args.py:74 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:304 bpython/urwid.py:553 +#: bpython/cli.py:321 bpython/urwid.py:555 msgid "y" msgstr "s" -#: bpython/cli.py:304 bpython/urwid.py:553 +#: bpython/cli.py:321 bpython/urwid.py:555 msgid "yes" msgstr "si" -#: bpython/cli.py:977 bpython/gtk_.py:483 -msgid "Cannot show source." -msgstr "Non è possibile mostrare il codice sorgente" +#: bpython/cli.py:1696 +msgid "Rewind" +msgstr "" -#: bpython/cli.py:1677 bpython/urwid.py:615 -#, python-format -msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " -msgstr " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" +#: bpython/cli.py:1697 +msgid "Save" +msgstr "" -#: bpython/gtk_.py:90 bpython/gtk_.py:115 -msgid "An error occurred." -msgstr "È stato riscontrato un errore" +#: bpython/cli.py:1698 +msgid "Pastebin" +msgstr "" -#: bpython/gtk_.py:97 -msgid "Exception details" -msgstr "Dettagli sull'eccezione" +#: bpython/cli.py:1699 +msgid "Pager" +msgstr "" -#: bpython/gtk_.py:149 -msgid "Statusbar" -msgstr "Barra di stato" +#: bpython/cli.py:1700 +msgid "Show Source" +msgstr "" -#: bpython/gtk_.py:226 -msgid "tooltip" -msgstr "tooltip" +#: bpython/curtsies.py:31 +msgid "log debug messages to bpython.log" +msgstr "" -#: bpython/gtk_.py:295 -msgid "File to save to" -msgstr "File nel quale salvare" +#: bpython/curtsies.py:33 +msgid "enter lines of file as though interactively typed" +msgstr "" -#: bpython/gtk_.py:306 -msgid "Python files" -msgstr "Files python" +#: bpython/history.py:236 +#, python-format +msgid "Error occurded while writing to file %s (%s)" +msgstr "" -#: bpython/gtk_.py:311 -msgid "All files" -msgstr "Tutti i files" +#: bpython/repl.py:471 +msgid "Nothing to get source of" +msgstr "" -#: bpython/gtk_.py:771 -msgid "gtk-specific options" -msgstr "Opzioni specifiche di gtk" +#: bpython/repl.py:476 +#, python-format +msgid "Cannot get source: %s" +msgstr "" + +#: bpython/repl.py:481 +#, python-format +msgid "Cannot access source of %r" +msgstr "" + +#: bpython/repl.py:483 +#, python-format +msgid "No source code found for %s" +msgstr "" -#: bpython/gtk_.py:772 -msgid "Options specific to bpython's Gtk+ front end" -msgstr "Opzioni specifiche riguardo il frontend gtk+ di bpython" +#: bpython/repl.py:649 +msgid "No clipboard available." +msgstr "" -#: bpython/gtk_.py:774 -msgid "Embed bpython" +#: bpython/repl.py:656 +msgid "Could not copy to clipboard." msgstr "" -#: bpython/gtk_.py:830 -msgid "Pastebin selection" +#: bpython/repl.py:658 +msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:741 +#: bpython/repl.py:667 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:668 msgid "Pastebin aborted" msgstr "" -#: bpython/repl.py:749 +#: bpython/repl.py:675 #, python-format msgid "Duplicate pastebin. Previous URL: %s" msgstr "" -#: bpython/repl.py:763 -#, python-format -msgid "Pastebin error for URL '%s': %s" -msgstr "" - -#: bpython/repl.py:767 bpython/repl.py:786 +#: bpython/repl.py:694 bpython/repl.py:721 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:772 +#: bpython/repl.py:699 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:781 bpython/repl.py:825 +#: bpython/repl.py:714 #, python-format -msgid "Pastebin URL: %s" +msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:798 +#: bpython/repl.py:733 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:801 +#: bpython/repl.py:736 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:808 +#: bpython/repl.py:743 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:812 +#: bpython/repl.py:747 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:819 -msgid "Upload failed: Failed to recognize the helper program's output as an URL." +#: bpython/repl.py:754 +msgid "" +"Upload failed: Failed to recognize the helper program's output as an URL." +msgstr "" + +#: bpython/repl.py:760 +#, python-format +msgid "Pastebin URL: %s" +msgstr "" + +#: bpython/repl.py:946 +msgid "Config file does not exist - create new from default? (y/N)" +msgstr "" + +#: bpython/urwid.py:617 +#, python-format +msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" +" <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" -#: bpython/urwid.py:1114 +#: bpython/urwid.py:1126 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1116 +#: bpython/urwid.py:1128 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1119 +#: bpython/urwid.py:1131 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1121 +#: bpython/urwid.py:1133 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1124 +#: bpython/urwid.py:1136 msgid "Port to run an eval server on (forces Twisted)." msgstr "" +#: bpython/curtsiesfrontend/repl.py:258 +msgid "Welcome to bpython!" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:258 +#, python-format +msgid "Press <%s> for help." +msgstr "" + +#~ msgid "An error occurred." +#~ msgstr "È stato riscontrato un errore" + +#~ msgid "Exception details" +#~ msgstr "Dettagli sull'eccezione" + +#~ msgid "Statusbar" +#~ msgstr "Barra di stato" + +#~ msgid "tooltip" +#~ msgstr "tooltip" + +#~ msgid "File to save to" +#~ msgstr "File nel quale salvare" + +#~ msgid "Python files" +#~ msgstr "Files python" + +#~ msgid "All files" +#~ msgstr "Tutti i files" + +#~ msgid "gtk-specific options" +#~ msgstr "Opzioni specifiche di gtk" + +#~ msgid "Options specific to bpython's Gtk+ front end" +#~ msgstr "Opzioni specifiche riguardo il frontend gtk+ di bpython" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index 678695723..b1b3f7a5f 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,172 +7,234 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2013-10-10 23:29+0200\n" -"PO-Revision-Date: 2013-10-11 14:47+0200\n" -"Last-Translator: Simon de Vlieger \n" +"POT-Creation-Date: 2015-01-17 18:57+0100\n" +"PO-Revision-Date: 2015-01-17 22:14+0100\n" +"Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" +"Language: nl_NL\n" +"X-Generator: Poedit 1.6.10\n" -#: bpython/args.py:53 +#: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back " -"to the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back to " +"the regular Python interpreter." msgstr "" -#: bpython/args.py:63 +#: bpython/args.py:67 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:65 +#: bpython/args.py:69 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:68 +#: bpython/args.py:72 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:70 +#: bpython/args.py:74 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:304 bpython/urwid.py:553 +#: bpython/cli.py:321 bpython/urwid.py:555 msgid "y" msgstr "j" -#: bpython/cli.py:304 bpython/urwid.py:553 +#: bpython/cli.py:321 bpython/urwid.py:555 msgid "yes" msgstr "ja" -#: bpython/cli.py:977 bpython/gtk_.py:483 -msgid "Cannot show source." -msgstr "Kan de broncode niet laden" +#: bpython/cli.py:1696 +msgid "Rewind" +msgstr "" -#: bpython/cli.py:1677 bpython/urwid.py:615 -#, python-format -msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " -msgstr " <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" +#: bpython/cli.py:1697 +msgid "Save" +msgstr "" -#: bpython/gtk_.py:90 bpython/gtk_.py:115 -msgid "An error occurred." -msgstr "Er is een fout opgetreden" +#: bpython/cli.py:1698 +msgid "Pastebin" +msgstr "" -#: bpython/gtk_.py:97 -msgid "Exception details" -msgstr "Fout details" +#: bpython/cli.py:1699 +msgid "Pager" +msgstr "" -#: bpython/gtk_.py:149 -msgid "Statusbar" -msgstr "Statusbalk" +#: bpython/cli.py:1700 +msgid "Show Source" +msgstr "" -#: bpython/gtk_.py:226 -msgid "tooltip" -msgstr "tooltip" +#: bpython/curtsies.py:31 +msgid "log debug messages to bpython.log" +msgstr "" + +#: bpython/curtsies.py:33 +msgid "enter lines of file as though interactively typed" +msgstr "" + +#: bpython/history.py:236 +#, python-format +msgid "Error occurded while writing to file %s (%s)" +msgstr "" -#: bpython/gtk_.py:295 -msgid "File to save to" -msgstr "Bestandsnaaam" +#: bpython/repl.py:471 +msgid "Nothing to get source of" +msgstr "" -#: bpython/gtk_.py:306 -msgid "Python files" -msgstr "Python bestanden" +#: bpython/repl.py:476 +#, python-format +msgid "Cannot get source: %s" +msgstr "" -#: bpython/gtk_.py:311 -msgid "All files" -msgstr "Alle bestanden" +#: bpython/repl.py:481 +#, python-format +msgid "Cannot access source of %r" +msgstr "" -#: bpython/gtk_.py:771 -msgid "gtk-specific options" -msgstr "gtk-specifieke opties" +#: bpython/repl.py:483 +#, python-format +msgid "No source code found for %s" +msgstr "" -#: bpython/gtk_.py:772 -msgid "Options specific to bpython's Gtk+ front end" -msgstr "Opties specifiek voor bpythons Gtk+ front end" +#: bpython/repl.py:649 +msgid "No clipboard available." +msgstr "" -#: bpython/gtk_.py:774 -msgid "Embed bpython" -msgstr "Embed bpython" +#: bpython/repl.py:656 +msgid "Could not copy to clipboard." +msgstr "" -#: bpython/gtk_.py:830 -msgid "Pastebin selection" -msgstr "Pastebin de selectie" +#: bpython/repl.py:658 +msgid "Copied content to clipboard." +msgstr "" -#: bpython/repl.py:741 +#: bpython/repl.py:667 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:668 msgid "Pastebin aborted" msgstr "" -#: bpython/repl.py:749 +#: bpython/repl.py:675 #, python-format msgid "Duplicate pastebin. Previous URL: %s" msgstr "" -#: bpython/repl.py:763 -#, python-format -msgid "Pastebin error for URL '%s': %s" -msgstr "" - -#: bpython/repl.py:767 bpython/repl.py:786 +#: bpython/repl.py:694 bpython/repl.py:721 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:772 +#: bpython/repl.py:699 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:781 bpython/repl.py:825 +#: bpython/repl.py:714 #, python-format -msgid "Pastebin URL: %s" +msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:798 +#: bpython/repl.py:733 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:801 +#: bpython/repl.py:736 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:808 +#: bpython/repl.py:743 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:812 +#: bpython/repl.py:747 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:819 -msgid "Upload failed: Failed to recognize the helper program's output as an URL." +#: bpython/repl.py:754 +msgid "" +"Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/urwid.py:1114 +#: bpython/repl.py:760 +#, python-format +msgid "Pastebin URL: %s" +msgstr "" + +#: bpython/repl.py:946 +msgid "Config file does not exist - create new from default? (y/N)" +msgstr "" + +#: bpython/urwid.py:617 +#, python-format +msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " +msgstr "" +" <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" + +#: bpython/urwid.py:1126 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1116 +#: bpython/urwid.py:1128 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1119 +#: bpython/urwid.py:1131 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1121 +#: bpython/urwid.py:1133 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1124 +#: bpython/urwid.py:1136 msgid "Port to run an eval server on (forces Twisted)." msgstr "" +#: bpython/curtsiesfrontend/repl.py:258 +msgid "Welcome to bpython!" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:258 +#, python-format +msgid "Press <%s> for help." +msgstr "" + +#~ msgid "An error occurred." +#~ msgstr "Er is een fout opgetreden" + +#~ msgid "Exception details" +#~ msgstr "Fout details" + +#~ msgid "Statusbar" +#~ msgstr "Statusbalk" + +#~ msgid "tooltip" +#~ msgstr "tooltip" + +#~ msgid "File to save to" +#~ msgstr "Bestandsnaaam" + +#~ msgid "Python files" +#~ msgstr "Python bestanden" + +#~ msgid "All files" +#~ msgstr "Alle bestanden" + +#~ msgid "gtk-specific options" +#~ msgstr "gtk-specifieke opties" + +#~ msgid "Options specific to bpython's Gtk+ front end" +#~ msgstr "Opties specifiek voor bpythons Gtk+ front end" + +#~ msgid "Pastebin selection" +#~ msgstr "Pastebin de selectie" From 6b7f1ddd101bd8e0a8dbe3170d7dafc666b0ed70 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 18 Jan 2015 13:58:17 +0100 Subject: [PATCH 0214/1650] Document skipping of slow tests Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/contributing.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 5263758ad..a1a46661a 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -86,6 +86,13 @@ To run tests from the bpython directory: $ nosetests +If you want to skip test cases that are known to be slow, run `nosetests` in the +following way: + +.. code-block:: bash + + $ nosetests -A "speed != 'slow'" + Building the documentation -------------------------- From 8693348ca729bf86b9c61379f10f6c1b5dfb81c8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 18 Jan 2015 13:58:48 +0100 Subject: [PATCH 0215/1650] Mark test_issue133 and test_issue108 as slow (fixes #449) Signed-off-by: Sebastian Ramacher --- bpython/test/test_crashers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index afb890c11..9ed275e08 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -95,6 +95,7 @@ def processExited(self, reason): usePTY=(master, slave, os.ttyname(slave))) return result + @attr(speed='slow') def test_issue108(self): input = textwrap.dedent( """\ @@ -105,6 +106,7 @@ def spam(): deferred = self.run_bpython(input) return deferred.addCallback(self.check_no_traceback) + @attr(speed='slow') def test_issue133(self): input = textwrap.dedent( """\ @@ -121,7 +123,6 @@ def check_no_traceback(self, data): class CursesCrashersTest(TrialTestCase, CrashersTest): backend = "cli" -@attr(speed='slow') @unittest.skipIf(not have_urwid, "urwid is not available") @unittest.skipIf(reactor is None, "twisted is not available") class UrwidCrashersTest(TrialTestCase, CrashersTest): From 0d0d4b434a7ca026aae24649d7010adc4758882b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 18 Jan 2015 14:12:29 +0100 Subject: [PATCH 0216/1650] Move install/script code to scripts Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 23 +++++++++++++++++++++++ .travis.script.sh | 11 +++++++++++ .travis.yml | 12 +----------- 3 files changed, 35 insertions(+), 11 deletions(-) create mode 100755 .travis.install.sh create mode 100755 .travis.script.sh diff --git a/.travis.install.sh b/.travis.install.sh new file mode 100755 index 000000000..7abe3d9ad --- /dev/null +++ b/.travis.install.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e +set -x + +pip install setuptools + +if [[ $RUN == nosetests ]] then + # core dependencies + pip install pygments requests 'curtsies >=0.1.16,<0.2.0' greenlet + # filewatch specific dependencies + pip install watchdog + # translation specific dependencies + pip install babel + # Python 2.6 specific dependencies + if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]] then + pip install unittest + fi + # build and install + python setup.py install +elif [[ $RUN == build_sphinx ]] then + # documentation specific dependencies + pip install sphinx +fi diff --git a/.travis.script.sh b/.travis.script.sh new file mode 100755 index 000000000..addd78e32 --- /dev/null +++ b/.travis.script.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e +set -x + +if [[ $RUN == build_sphinx ]] then + python setup.py build_sphinx + python setup.py build_sphinx_man +elif [[ $RUN == nosetests ]] then + cd build/lib/ + nosetests bpython/test +fi diff --git a/.travis.yml b/.travis.yml index 1d56edc97..c765ecd6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,17 +12,7 @@ env: - RUN=build_sphinx install: - - pip install setuptools - # core dependencies - - if [[ $RUN == nosetests ]]; then pip install pygments requests; fi - # curtsies specific dependencies - - if [[ $RUN == nosetests ]]; then pip install 'curtsies >=0.1.16,<0.2.0' greenlet watchdog; fi - # translation specific dependencies - - if [[ $RUN == nosetests ]]; then pip install babel; fi - # documentation specific dependencies - - if [[ $RUN == build_sphinx ]]; then pip install sphinx; fi - - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]] && [[ $RUN == nosetests ]]; then pip install unittest2; fi - - if [[ $RUN == nosetests ]]; then python setup.py install; fi + - ./.travis.install.sh script: - if [[ $RUN == build_sphinx ]]; then python setup.py build_sphinx; fi From 573c8d341ad011b18a13b6bb018b2ea750a016c3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 18 Jan 2015 14:15:32 +0100 Subject: [PATCH 0217/1650] Install dependencies for crasher test Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.install.sh b/.travis.install.sh index 7abe3d9ad..ebe78c7b0 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -15,6 +15,8 @@ if [[ $RUN == nosetests ]] then if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]] then pip install unittest fi + # dependencies for crasher tests + pip install Twisted urwid # build and install python setup.py install elif [[ $RUN == build_sphinx ]] then From 30c0dcbe1dd4a183f0e9bd1a795da9b8eef32f43 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 18 Jan 2015 14:15:41 +0100 Subject: [PATCH 0218/1650] Run nosetests with -v Signed-off-by: Sebastian Ramacher --- .travis.script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.script.sh b/.travis.script.sh index addd78e32..43b52299d 100755 --- a/.travis.script.sh +++ b/.travis.script.sh @@ -7,5 +7,5 @@ if [[ $RUN == build_sphinx ]] then python setup.py build_sphinx_man elif [[ $RUN == nosetests ]] then cd build/lib/ - nosetests bpython/test + nosetests -v bpython/test fi From f2709c3c71b0a468dbff70d9586eaadae4b3b41d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 18 Jan 2015 14:17:17 +0100 Subject: [PATCH 0219/1650] Missing semi-colons Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 6 +++--- .travis.script.sh | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index ebe78c7b0..a72680355 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -4,7 +4,7 @@ set -x pip install setuptools -if [[ $RUN == nosetests ]] then +if [[ $RUN == nosetests ]]; then # core dependencies pip install pygments requests 'curtsies >=0.1.16,<0.2.0' greenlet # filewatch specific dependencies @@ -12,14 +12,14 @@ if [[ $RUN == nosetests ]] then # translation specific dependencies pip install babel # Python 2.6 specific dependencies - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]] then + if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest fi # dependencies for crasher tests pip install Twisted urwid # build and install python setup.py install -elif [[ $RUN == build_sphinx ]] then +elif [[ $RUN == build_sphinx ]]; then # documentation specific dependencies pip install sphinx fi diff --git a/.travis.script.sh b/.travis.script.sh index 43b52299d..f8619bb48 100755 --- a/.travis.script.sh +++ b/.travis.script.sh @@ -2,10 +2,10 @@ set -e set -x -if [[ $RUN == build_sphinx ]] then +if [[ $RUN == build_sphinx ]]; then python setup.py build_sphinx python setup.py build_sphinx_man -elif [[ $RUN == nosetests ]] then +elif [[ $RUN == nosetests ]]; then cd build/lib/ nosetests -v bpython/test fi From bb0044d0621aaf44794da5da6294402e261fb576 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 18 Jan 2015 14:22:39 +0100 Subject: [PATCH 0220/1650] Install crasher test cases only on Python 2.x Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 8 ++++++-- .travis.yml | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index a72680355..e19daaf72 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -13,10 +13,14 @@ if [[ $RUN == nosetests ]]; then pip install babel # Python 2.6 specific dependencies if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then - pip install unittest + pip install unittest2 fi # dependencies for crasher tests - pip install Twisted urwid + case $TRAVIS_PYTHON_VERSION in + 2*) + pip install Twisted urwid + ;; + esac # build and install python setup.py install elif [[ $RUN == build_sphinx ]]; then diff --git a/.travis.yml b/.travis.yml index c765ecd6f..a2ad7aa6a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,4 @@ install: - ./.travis.install.sh script: - - if [[ $RUN == build_sphinx ]]; then python setup.py build_sphinx; fi - - if [[ $RUN == build_sphinx ]]; then python setup.py build_sphinx_man; fi - - if [[ $RUN == nosetests ]]; then cd build/lib/ && nosetests bpython/test; fi + - ./.travis.script.sh From 81d6044c0917cad157d103fab5aa3b25863a017a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 18 Jan 2015 15:53:30 +0100 Subject: [PATCH 0221/1650] Add failing test for #447 Signed-off-by: Sebastian Ramacher --- bpython/test/test_config.py | 46 +++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index 9bacc1f06..23db0c645 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -1,12 +1,27 @@ import os import unittest import tempfile +import textwrap from bpython import config TEST_THEME_PATH = os.path.join(os.path.dirname(__file__), "test.theme") class TestConfig(unittest.TestCase): + def load_temp_config(self, content, struct=None): + """Write config to a temporary file and load it.""" + + if struct is None: + struct = config.Struct() + + with tempfile.NamedTemporaryFile() as f: + f.write(content.encode('utf8')) + f.flush() + + config.loadini(struct, f.name) + + return struct + def test_load_theme(self): struct = config.Struct() struct.color_scheme = dict() @@ -19,13 +34,30 @@ def test_load_theme(self): config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, defaults) self.assertEquals(struct.color_scheme, expected) - def test_load_config(self): - struct = config.Struct() - with tempfile.NamedTemporaryFile() as f: - f.write(''.encode('utf8')) - f.write('[keyboard]\nhelp = C-h\n'.encode('utf8')) - f.flush() - config.loadini(struct, f.name) + def test_keybindings_use_default(self): + struct = self.load_temp_config(textwrap.dedent(""" + [keyboard] + help = F1 + """)) + + self.assertEqual(struct.help_key, 'F1') + + def test_keybindings_use_other_default(self): + struct = self.load_temp_config(textwrap.dedent(""" + [keyboard] + help = C-h + """)) + self.assertEqual(struct.help_key, 'C-h') self.assertEqual(struct.backspace_key, '') + def test_keybindings_use_other_default_issue_447(self): + struct = self.load_temp_config(textwrap.dedent(""" + [keyboard] + help = F2 + show_source = F9 + """)) + + self.assertEqual(struct.help_key, 'F2') + self.assertEqual(struct.show_source_key, 'F9') + From fe49677e37551bf9ca6c1f174ee6db68491c93f6 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 18 Jan 2015 04:11:08 -0500 Subject: [PATCH 0222/1650] indent instead of removing empty pasted lines only indents, doesn't add lines for mutliple commands in one paste only works correctly for pastes beginning on an empty line --- bpython/curtsiesfrontend/interpreter.py | 16 ++++++ bpython/curtsiesfrontend/preprocess.py | 28 ++++++++++ bpython/curtsiesfrontend/repl.py | 68 ++--------------------- bpython/test/fodder/__init__.py | 0 bpython/test/fodder/original.py | 22 ++++++++ bpython/test/fodder/processed.py | 20 +++++++ bpython/test/test_curtsies_repl.py | 22 +++++--- bpython/test/test_interpreter.py | 12 ---- bpython/test/test_preprocess.py | 73 +++++++++++++++++++++++++ 9 files changed, 179 insertions(+), 82 deletions(-) create mode 100644 bpython/curtsiesfrontend/preprocess.py create mode 100644 bpython/test/fodder/__init__.py create mode 100644 bpython/test/fodder/original.py create mode 100644 bpython/test/fodder/processed.py create mode 100644 bpython/test/test_preprocess.py diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 850674a85..e964109a0 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -153,3 +153,19 @@ def format(self, tbtext, lexer): else: cur_line.append((token,text)) assert cur_line == [], cur_line + + +def code_finished_will_parse(s, compiler): + """Returns a tuple of whether the buffer could be complete and whether it will parse + + True, True means code block is finished and no predicted parse error + True, False means code block is finished because a parse error is predicted + False, True means code block is unfinished + False, False isn't possible - an predicted error makes code block done""" + try: + finished = bool(compiler(s)) + code_will_parse = True + except (ValueError, SyntaxError, OverflowError): + finished = True + code_will_parse = False + return finished, code_will_parse diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py new file mode 100644 index 000000000..93329ef34 --- /dev/null +++ b/bpython/curtsiesfrontend/preprocess.py @@ -0,0 +1,28 @@ +"""Tools for preparing code to be run in the REPL (removing blank lines, etc)""" +import re + +from bpython.curtsiesfrontend.interpreter import code_finished_will_parse + +#TODO specifically catch IndentationErrors instead of any syntax errors + +def indent_empty_lines(s, compiler): + """Indents blank lines that would otherwise cause early compilation + + Only really works if starting on a new line""" + lines = s.split('\n') + ends_with_newline = False + if lines and not lines[-1]: + ends_with_newline = True + lines.pop() + result_lines = [] + + for p_line, line, n_line in zip([''] + lines[:-1], lines, lines[1:] + ['']): + if len(line) == 0: + p_indent = re.match(r'\s*', p_line).group() + n_indent = re.match(r'\s*', n_line).group() + result_lines.append(min([p_indent, n_indent], key=len) + line) + else: + result_lines.append(line) + + return '\n'.join(result_lines) + ('\n' if ends_with_newline else '') + diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index efc4dfb4f..07282c61d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1,4 +1,3 @@ -import code import contextlib import errno import functools @@ -17,7 +16,6 @@ from pygments import format from bpython._py3compat import PythonLexer from pygments.formatters import TerminalFormatter -from interpreter import Interp import blessings @@ -45,6 +43,8 @@ from bpython.curtsiesfrontend import events as bpythonevents from bpython.curtsiesfrontend.parse import parse as bpythonparse from bpython.curtsiesfrontend.parse import func_for_letter, color_for_letter +from bpython.curtsiesfrontend.preprocess import indent_empty_lines +from bpython.curtsiesfrontend.interpreter import Interp, code_finished_will_parse #TODO other autocomplete modes (also fix in other bpython implementations) @@ -457,7 +457,7 @@ def process_control_event(self, e): if ctrl_char is not None: return self.process_event(ctrl_char) simple_events = just_simple_events(e.events) - source = bad_empty_lines_removed(''.join(simple_events)) + source = indent_empty_lines(''.join(simple_events), self.interp.compile) with self.in_paste_mode(): for ee in source: @@ -713,7 +713,7 @@ def send_session_to_external_editor(self, filename=None): text = self.send_to_external_editor(for_editor) lines = text.split('\n') from_editor = [line for line in lines if line[:4] != '### '] - source = bad_empty_lines_removed('\n'.join(from_editor)) + source = indent_empty_lines('\n'.join(from_editor), self.interp.compile) self.history = source.split('\n') self.reevaluate(insert_into_history=True) self.current_line = lines[-1][4:] @@ -828,7 +828,8 @@ def push(self, line, insert_into_history=True): code_to_run = '\n'.join(self.buffer) logger.debug('running %r in interpreter', self.buffer) - c, code_will_parse = code_finished_will_parse('\n'.join(self.buffer)) + c, code_will_parse = code_finished_will_parse('\n'.join(self.buffer), + self.interp.compile) self.saved_predicted_parse_error = not code_will_parse if c: logger.debug('finished - buffer cleared') @@ -1429,63 +1430,6 @@ def just_simple_events(event_list): simple_events.append(e) return simple_events -def code_finished_will_parse(s): - """Returns a tuple of whether the buffer could be complete and whether it will parse - - True, True means code block is finished and no predicted parse error - True, False means code block is finished because a parse error is predicted - False, True means code block is unfinished - False, False isn't possible - an predicted error makes code block done""" - try: - finished = bool(code.compile_command(s)) - code_will_parse = True - except (ValueError, SyntaxError, OverflowError): - finished = True - code_will_parse = False - return finished, code_will_parse - -def bad_empty_lines_removed(s): - """Removes empty lines that would cause unfinished input to be evaluated""" - # If there's a syntax error followed by an empty line, remove the empty line - lines = s.split('\n') - #TODO this should be our interpreter object making this decision so it - # can be compiler directive (__future__ statement) -aware - #TODO specifically catch IndentationErrors instead of any syntax errors - - current_block = [] - complete_blocks = [] - for i, line in enumerate(s.split('\n')): - current_block.append(line) - could_be_finished, valid = code_finished_will_parse('\n'.join(current_block)) - if could_be_finished and valid: - complete_blocks.append(current_block) - current_block = [] - continue - elif could_be_finished and not valid: - if complete_blocks: - complete_blocks[-1].extend(current_block) - current_block = complete_blocks.pop() - if len(current_block) < 2: - return s #TODO return partial result instead of giving up - last_line = current_block.pop(len(current_block) - 2) - assert not last_line, last_line - new_finished, new_valid = code_finished_will_parse('\n'.join(current_block)) - if new_valid and new_finished: - complete_blocks.append(current_block) - current_block = [] - elif new_valid: - continue - else: - return s #TODO return partial result instead of giving up - - else: - return s #TODO return partial result instead of giving up - else: - continue - return '\n'.join(['\n'.join(block) - for block in complete_blocks + [current_block] - if block]) - #TODO this needs some work to function again and be useful for embedding def simple_repl(): diff --git a/bpython/test/fodder/__init__.py b/bpython/test/fodder/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/bpython/test/fodder/original.py b/bpython/test/fodder/original.py new file mode 100644 index 000000000..76d1f247e --- /dev/null +++ b/bpython/test/fodder/original.py @@ -0,0 +1,22 @@ +# careful: whitespace is very important in this file +# also, this code runs - so everything should be a noop + +class BlankLineBetweenMethods(object): + def method1(self): + pass + + def method2(self): + pass + +def BlankLineInFunction(self): + return 7 + + pass + +#StartTest-blank_lines_in_for_loop +for i in range(2): + pass + + pass +#EndTest + diff --git a/bpython/test/fodder/processed.py b/bpython/test/fodder/processed.py new file mode 100644 index 000000000..d69976d34 --- /dev/null +++ b/bpython/test/fodder/processed.py @@ -0,0 +1,20 @@ +#careful! Whitespace is very important in this file + +class BlankLineBetweenMethods(object): + def method1(self): + pass + + def method2(self): + pass + +def BlankLineInFunction(self): + return 7 + + pass + +#StartTest-blank_lines_in_for_loop +for i in range(2): + pass + + pass +#EndTest diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 344cfcb20..c137632d9 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -1,10 +1,11 @@ # coding: utf8 import code +from contextlib import contextmanager +from functools import partial import os +from StringIO import StringIO import sys import tempfile -from contextlib import contextmanager -from StringIO import StringIO import unittest try: @@ -16,6 +17,7 @@ def skip(f): py3 = (sys.version_info[0] == 3) from bpython.curtsiesfrontend import repl as curtsiesrepl +from bpython.curtsiesfrontend import interpreter from bpython import config from bpython import args @@ -28,24 +30,28 @@ def setup_config(conf): setattr(config_struct, key, value) return config_struct + class TestCurtsiesRepl(unittest.TestCase): def setUp(self): self.repl = create_repl() + def cfwp(self, source): + return interpreter.code_finished_will_parse(source, self.repl.interp.compile) + def test_code_finished_will_parse(self): self.repl.buffer = ['1 + 1'] - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True)) + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) self.repl.buffer = ['def foo(x):'] - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (False, True)) + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (False, True)) self.repl.buffer = ['def foo(x)'] - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, False)) + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False)) self.repl.buffer = ['def foo(x):', 'return 1'] - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, False)) + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False)) self.repl.buffer = ['def foo(x):', ' return 1'] - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True)) + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) self.repl.buffer = ['def foo(x):', ' return 1', ''] - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True)) + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) def test_external_communication(self): self.assertEqual(type(self.repl.help_text()), type(b'')) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 56a8806e5..60922f310 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -1,7 +1,6 @@ import unittest from bpython.curtsiesfrontend import interpreter -from bpython.curtsiesfrontend.repl import bad_empty_lines_removed from curtsies.fmtfuncs import * class TestInterpreter(unittest.TestCase): @@ -42,14 +41,3 @@ def g(): self.assertEquals(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) -class TestPreprocessing(unittest.TestCase): - def test_bad_empty_lines_removed(self): - self.assertEqual(bad_empty_lines_removed("def foo():\n" - " return 1\n" - "\n" - " pass\n"), - "def foo():\n" - " return 1\n" - " pass\n") - - diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py new file mode 100644 index 000000000..3445f3212 --- /dev/null +++ b/bpython/test/test_preprocess.py @@ -0,0 +1,73 @@ +from code import compile_command as compiler +from functools import partial +import difflib +import inspect +import re +import unittest + +from bpython.curtsiesfrontend.interpreter import code_finished_will_parse +from bpython.curtsiesfrontend.preprocess import indent_empty_lines + +from bpython.test.fodder import original as original, processed + +indent_empty = partial(indent_empty_lines, compiler=compiler) + + +def get_fodder_source(test_name): + pattern = r'#StartTest-%s\n(.*?)#EndTest' % (test_name,) + print repr(pattern) + orig, xformed = [re.search(pattern, inspect.getsource(module), re.DOTALL) + for module in [original, processed]] + + if not orig: + raise ValueError("Can't locate test %s in original fodder file" % (test_name,)) + if not xformed: + raise ValueError("Can't locate test %s in processed fodder file" % (test_name,)) + return orig.group(1), xformed.group(1) + + +class TestPreprocessing(unittest.TestCase): + + def assertCompiles(self, source): + finished, parsable = code_finished_will_parse(source, compiler) + return finished and parsable + + def test_indent_empty_lines_nops(self): + self.assertEqual(indent_empty('hello'), 'hello') + + def assertShowWhitespaceEqual(self, a, b): + self.assertEqual( + indent_empty(a), b, + ''.join(difflib.context_diff(a.replace(' ', '~').splitlines(True), + b.replace(' ', '~').splitlines(True), + fromfile='original', + tofile='processed', + n=5))) + + def assertDefinitionIndented(self, obj): + name = obj.__name__ + obj2 = getattr(processed, name) + orig = inspect.getsource(obj) + xformed = inspect.getsource(obj2) + self.assertShowWhitespaceEqual(indent_empty(orig), xformed) + self.assertCompiles(xformed) + + def assertLinesIndented(self, test_name): + orig, xformed = get_fodder_source(test_name) + self.assertShowWhitespaceEqual(indent_empty(orig), xformed) + self.assertCompiles(xformed) + + def assertIndented(self, obj_or_name): + if isinstance(obj_or_name, str): + self.assertLinesIndented(obj_or_name) + else: + self.assertDefinitionIndented(obj_or_name) + + def test_empty_line_between_methods(self): + self.assertIndented(original.BlankLineBetweenMethods) + + def test_empty_line_within_class(self): + self.assertIndented(original.BlankLineInFunction) + + def test_blank_lines_in_for_loop(self): + self.assertIndented('blank_lines_in_for_loop') From 62fe2441df7fc341e8fc3cc8251c56b6d37980bb Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 18 Jan 2015 04:35:41 -0500 Subject: [PATCH 0223/1650] add skipped tests add tests for examples of why the current naive approach isn't sufficient --- bpython/test/fodder/original.py | 25 +++++++++++++++++++++++++ bpython/test/fodder/processed.py | 25 +++++++++++++++++++++++++ bpython/test/test_preprocess.py | 26 +++++++++++++++++++++++--- 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/bpython/test/fodder/original.py b/bpython/test/fodder/original.py index 76d1f247e..f3c6f956c 100644 --- a/bpython/test/fodder/original.py +++ b/bpython/test/fodder/original.py @@ -20,3 +20,28 @@ def BlankLineInFunction(self): pass #EndTest +#StartTest-blank_line_in_try_catch +try: + 1 + +except: + 2 +#EndTest + +#StartTest-blank_line_in_try_catch_else +try: + 1 + +except: + 2 + +else: + 3 +#EndTest + +#StartTest-blank_trailing_line +def foo(): + return 1 + +#EndTest + diff --git a/bpython/test/fodder/processed.py b/bpython/test/fodder/processed.py index d69976d34..c4474856d 100644 --- a/bpython/test/fodder/processed.py +++ b/bpython/test/fodder/processed.py @@ -18,3 +18,28 @@ def BlankLineInFunction(self): pass #EndTest + +#StartTest-blank_line_in_try_catch +try: + 1 + +except: + 2 +#EndTest + +#StartTest-blank_line_in_try_catch_else +try: + 1 + +except: + 2 + +else: + 3 +#EndTest + +#StartTest-blank_trailing_line +def foo(): + return 1 + +#EndTest diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index 3445f3212..b79b4de45 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -5,6 +5,12 @@ import re import unittest +try: + import unittest2 as unittest +except ImportError: + import unittest +skip = unittest.skip + from bpython.curtsiesfrontend.interpreter import code_finished_will_parse from bpython.curtsiesfrontend.preprocess import indent_empty_lines @@ -34,14 +40,17 @@ def assertCompiles(self, source): def test_indent_empty_lines_nops(self): self.assertEqual(indent_empty('hello'), 'hello') + self.assertEqual(indent_empty('hello\ngoodbye'), 'hello\ngoodbye') + self.assertEqual(indent_empty('a\n b\nc\n'), 'a\n b\nc\n') def assertShowWhitespaceEqual(self, a, b): + print a self.assertEqual( - indent_empty(a), b, + a, b, ''.join(difflib.context_diff(a.replace(' ', '~').splitlines(True), b.replace(' ', '~').splitlines(True), - fromfile='original', - tofile='processed', + fromfile='actual', + tofile='expected', n=5))) def assertDefinitionIndented(self, obj): @@ -71,3 +80,14 @@ def test_empty_line_within_class(self): def test_blank_lines_in_for_loop(self): self.assertIndented('blank_lines_in_for_loop') + + @skip("More advanced technique required: need to try compiling and backtracking") + def test_blank_line_in_try_catch(self): + self.assertIndented('blank_line_in_try_catch') + + @skip("More advanced technique required: need to try compiling and backtracking") + def test_blank_line_in_try_catch_else(self): + self.assertIndented('blank_line_in_try_catch_else') + + def test_blank_trailing_line(self): + self.assertIndented('blank_trailing_line') From 7fe3a601c69bf3e03e376e665712c427cb28746f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 18 Jan 2015 15:37:10 +0100 Subject: [PATCH 0224/1650] Add bpython.test.fodder package Signed-off-by: Sebastian Ramacher --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index ca044ed10..f3118008a 100755 --- a/setup.py +++ b/setup.py @@ -181,6 +181,7 @@ def initialize_options(self): 'bpython', 'bpython.curtsiesfrontend', 'bpython.test', + 'bpython.test.fodder', 'bpython.translations', 'bpdb' ] From 74382d4bb814a1aa5f8308fd8c6733e97968fd76 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 18 Jan 2015 18:41:34 -0500 Subject: [PATCH 0225/1650] remove prints from tests --- bpython/test/test_preprocess.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index b79b4de45..dc12558ae 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -21,7 +21,6 @@ def get_fodder_source(test_name): pattern = r'#StartTest-%s\n(.*?)#EndTest' % (test_name,) - print repr(pattern) orig, xformed = [re.search(pattern, inspect.getsource(module), re.DOTALL) for module in [original, processed]] @@ -44,7 +43,6 @@ def test_indent_empty_lines_nops(self): self.assertEqual(indent_empty('a\n b\nc\n'), 'a\n b\nc\n') def assertShowWhitespaceEqual(self, a, b): - print a self.assertEqual( a, b, ''.join(difflib.context_diff(a.replace(' ', '~').splitlines(True), From f8f4e47b03c5f4ab4607bca68111543974f4c9ed Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 18 Jan 2015 15:04:13 -0500 Subject: [PATCH 0226/1650] fix #444 filename completion going too deep adds some tests of tab key behavior, but doesn't add test for buggy filename completion behavior --- bpython/curtsiesfrontend/repl.py | 14 +++--- bpython/repl.py | 7 ++- bpython/test/test_curtsies_repl.py | 69 ++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 07282c61d..abf49f01d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -624,10 +624,10 @@ def on_tab(self, back=False): def only_whitespace_left_of_cursor(): """returns true if all characters on current line before cursor are whitespace""" - return self.current_line[:self.cursor_offset].strip() + return not self.current_line[:self.cursor_offset].strip() logger.debug('self.matches_iter.matches: %r', self.matches_iter.matches) - if not only_whitespace_left_of_cursor(): + if only_whitespace_left_of_cursor(): front_white = (len(self.current_line[:self.cursor_offset]) - len(self.current_line[:self.cursor_offset].lstrip())) to_add = 4 - (front_white % self.config.tab_length) @@ -635,15 +635,18 @@ def only_whitespace_left_of_cursor(): self.add_normal_character(' ') return - # run complete() if we aren't already iterating through matches - if not self.matches_iter: + #if not self.matches_iter.candidate_selected: + # self.list_win_visible = self.complete(tab=True) + + # run complete() if we don't already have matches + if len(self.matches_iter.matches) == 0: self.list_win_visible = self.complete(tab=True) # 3. check to see if we can expand the current word if self.matches_iter.is_cseq(): self._cursor_offset, self._current_line = self.matches_iter.substitute_cseq() # using _current_line so we don't trigger a completion reset - if not self.matches_iter: + if not self.matches_iter.matches: self.list_win_visible = self.complete() elif self.matches_iter.matches: @@ -651,6 +654,7 @@ def only_whitespace_left_of_cursor(): or self.matches_iter.next() self._cursor_offset, self._current_line = self.matches_iter.cur_line() # using _current_line so we don't trigger a completion reset + self.list_win_visible = True def on_control_d(self): if self.current_line == '': diff --git a/bpython/repl.py b/bpython/repl.py index 0ad61f560..c5937b581 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -159,6 +159,11 @@ def __nonzero__(self): """MatchesIterator is False when word hasn't been replaced yet""" return self.index != -1 + @property + def candidate_selected(self): + """True when word selected/replaced, False when word hasn't been replaced yet""" + return bool(self) + def __iter__(self): return self @@ -541,7 +546,7 @@ def complete(self, tab=False): if len(matches) == 1: if tab: # if this complete is being run for a tab key press, substitute common sequence self._cursor_offset, self._current_line = self.matches_iter.substitute_cseq() - return Repl.complete(self) + return Repl.complete(self) # again for elif self.matches_iter.current_word == matches[0]: self.matches_iter.clear() return False diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index c137632d9..a33bcaa7a 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -2,6 +2,7 @@ import code from contextlib import contextmanager from functools import partial +from mock import Mock import os from StringIO import StringIO import sys @@ -18,6 +19,7 @@ def skip(f): from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter +from bpython import autocomplete from bpython import config from bpython import args @@ -84,6 +86,73 @@ def test_get_last_word_with_prev_line(self): self.repl.up_one_line() self.assertEqual(self.repl.current_line,'2 3') + +class TestCurtsiesReplTab(unittest.TestCase): + + def setUp(self): + self.repl = create_repl() + self.repl.matches_iter = Mock() + def add_matches(*args, **kwargs): + self.repl.matches_iter.matches = ['aaa', 'aab', 'aac'] + self.repl.complete = Mock(side_effect=add_matches, + return_value=True) + + def test_tab_with_no_matches_triggers_completion(self): + self.repl._current_line = ' asdf' + self.repl._cursor_offset = 5 + self.repl.matches_iter.matches = [] + self.repl.matches_iter.is_cseq.return_value = False + self.repl.matches_iter.cur_line.return_value = (None, None) + self.repl.on_tab() + self.repl.complete.assert_called_once_with(tab=True) + + def test_tab_after_indentation_adds_space(self): + self.repl._current_line = ' ' + self.repl._cursor_offset = 4 + self.repl.on_tab() + self.assertEqual(self.repl._current_line, ' ') + self.assertEqual(self.repl._cursor_offset, 8) + + def test_tab_at_beginning_of_line_adds_space(self): + self.repl._current_line = '' + self.repl._cursor_offset = 0 + self.repl.on_tab() + self.assertEqual(self.repl._current_line, ' ') + self.assertEqual(self.repl._cursor_offset, 4) + + def test_tab_with_no_matches_selects_first(self): + self.repl._current_line = ' aa' + self.repl._cursor_offset = 3 + self.repl.matches_iter.matches = [] + self.repl.matches_iter.is_cseq.return_value = False + self.repl.matches_iter.next.return_value = None + self.repl.matches_iter.cur_line.return_value = (None, None) + self.repl.on_tab() + self.repl.complete.assert_called_once_with(tab=True) + self.repl.matches_iter.next.assert_called_once_with() + self.repl.matches_iter.cur_line.assert_called_once_with() + + def test_tab_with_matches_selects_next_match(self): + self.repl._current_line = ' aa' + self.repl._cursor_offset = 3 + self.repl.complete() + self.repl.matches_iter.is_cseq.return_value = False + self.repl.matches_iter.next.return_value = None + self.repl.matches_iter.cur_line.return_value = (None, None) + self.repl.on_tab() + self.repl.matches_iter.next.assert_called_once_with() + self.repl.matches_iter.cur_line.assert_called_once_with() + + def test_tab_completes_common_sequence(self): + self.repl._current_line = ' a' + self.repl._cursor_offset = 2 + self.repl.matches_iter.matches = ['aaa', 'aab', 'aac'] + self.repl.matches_iter.is_cseq.return_value = True + self.repl.matches_iter.substitute_cseq.return_value = (None, None) + self.repl.on_tab() + self.repl.matches_iter.substitute_cseq.assert_called_once_with() + + @contextmanager # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy def captured_output(): new_out, new_err = StringIO(), StringIO() From 01b98f11e09234f49a99142317a75d471d681cbf Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 19 Jan 2015 00:28:34 -0500 Subject: [PATCH 0227/1650] Add tests for issue #444 --- bpython/test/test_curtsies_repl.py | 42 +++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index a33bcaa7a..184ac5125 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -2,7 +2,7 @@ import code from contextlib import contextmanager from functools import partial -from mock import Mock +from mock import Mock, patch import os from StringIO import StringIO import sys @@ -153,6 +153,46 @@ def test_tab_completes_common_sequence(self): self.repl.matches_iter.substitute_cseq.assert_called_once_with() +class TestCurtsiesReplFilenameCompletion(unittest.TestCase): + def setUp(self): + self.repl = create_repl() + + def test_list_win_visible_and_match_selected_on_tab_when_multiple_options(self): + self.repl._current_line = " './'" + self.repl._cursor_offset = 2 + with patch('bpython.autocomplete.get_completer_bpython') as mock: + mock.return_value = (['./abc', './abcd', './bcd'], autocomplete.FilenameCompletion) + self.repl.update_completion() + self.assertEqual(self.repl.list_win_visible, False) + self.repl.on_tab() + self.assertEqual(self.repl.current_match, './abc') + self.assertEqual(self.repl.list_win_visible, True) + + def test_list_win_not_visible_and_cseq_if_cseq(self): + self.repl._current_line = " './a'" + self.repl._cursor_offset = 5 + with patch('bpython.autocomplete.get_completer_bpython') as mock: + mock.return_value = (['./abcd', './abce'], autocomplete.FilenameCompletion) + self.repl.update_completion() + self.assertEqual(self.repl.list_win_visible, False) + self.repl.on_tab() + self.assertEqual(self.repl._current_line, " './abc'") + self.assertEqual(self.repl.current_match, None) + self.assertEqual(self.repl.list_win_visible, False) + + def test_list_win_not_visible_and_match_selected_if_one_option(self): + self.repl._current_line = " './a'" + self.repl._cursor_offset = 5 + with patch('bpython.autocomplete.get_completer_bpython') as mock: + mock.return_value = (['./abcd'], autocomplete.FilenameCompletion) + self.repl.update_completion() + self.assertEqual(self.repl.list_win_visible, False) + self.repl.on_tab() + self.assertEqual(self.repl._current_line, " './abcd'") + self.assertEqual(self.repl.current_match, None) + self.assertEqual(self.repl.list_win_visible, False) + + @contextmanager # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy def captured_output(): new_out, new_err = StringIO(), StringIO() From cecd6f78364a805db0e0b8eeb6a612378041d243 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Jan 2015 17:34:01 +0100 Subject: [PATCH 0228/1650] Revert "Do not throw if PYTHONSTARTUPFILE does not exists (fixes #438)" This reverts commit 3a86eaee6f0375a301d79ba8dfdf98ee535a1450. --- bpython/curtsiesfrontend/repl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index abf49f01d..7efd38544 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -415,6 +415,8 @@ def startup(self): self.interp.runsource(f.read(), filename, 'exec') else: self.interp.runsource(f.read(), filename, 'exec') + else: + raise IOError("Python startup file (PYTHONSTARTUP) not found at %s" % filename) def clean_up_current_line_for_exit(self): """Called when trying to exit to prep for final paint""" From 7474450d93b0ae1d1ee5c6aab1bbb5fad10ad2bb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Jan 2015 17:51:49 +0100 Subject: [PATCH 0229/1650] Add event to run PYTHONSTARTUP Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/events.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index 299326135..3b2b3c755 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -34,3 +34,6 @@ def __init__(self, when, who='?'): def __repr__(self): return ("" % (self.who, self.when - time.time())) + +class RunStartupFileEvent(curtsies.events.Event): + """Reqeust to run the startup file.""" From cf5f8d72fc24fd81f9fcf42049d871020a90877d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Jan 2015 17:53:35 +0100 Subject: [PATCH 0230/1650] Run PYTHONSTARTUP using events Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 5 +++++ bpython/curtsiesfrontend/repl.py | 25 +++++++++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index f243eaff5..953fcafb7 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -134,6 +134,11 @@ def process_event(e): scrolled = window.render_to_terminal(array, cursor_pos) repl.scroll_offset += scrolled + # run startup file + if interactive: + process_event(bpythonevents.RunStartupFileEvent()) + + # handle paste if paste: process_event(paste) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 7efd38544..5caa4302c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -292,8 +292,6 @@ def smarter_request_reload(files_modified=()): logger.debug("starting parent init") super(Repl, self).__init__(interp, config) #TODO bring together all interactive stuff - including current directory in path? - if interactive: - self.startup() self.formatter = BPythonFormatter(config.color_scheme) self.interact = self.status_bar # overwriting what bpython.Repl put there # interact is called to interact with the status bar, @@ -401,22 +399,19 @@ def sigtstp_handler(self, signum, frame): self.after_suspend() self.__enter__() - def startup(self): + def run_startup(self): """ Execute PYTHONSTARTUP file if it exits. Call this after front end-specific initialisation. """ filename = os.environ.get('PYTHONSTARTUP') if filename: - if os.path.isfile(filename): - with open(filename, 'r') as f: - if py3: - #TODO runsource has a new signature in PY3 - self.interp.runsource(f.read(), filename, 'exec') - else: - self.interp.runsource(f.read(), filename, 'exec') - else: - raise IOError("Python startup file (PYTHONSTARTUP) not found at %s" % filename) + with open(filename, 'r') as f: + if py3: + #TODO runsource has a new signature in PY3 + self.interp.runsource(f.read(), filename, 'exec') + else: + self.interp.runsource(f.read(), filename, 'exec') def clean_up_current_line_for_exit(self): """Called when trying to exit to prep for final paint""" @@ -468,6 +463,12 @@ def process_control_event(self, e): else: self.process_simple_keypress(ee) + elif isinstance(e, bpythonevents.RunStartupFileEvent): + try: + self.run_startup() + except IOError as e: + self.status_bar.message(_('Executing PYTHONSTARTUP failed: %s') % (str(e))) + elif self.stdin.has_focus: return self.stdin.process_event(e) From ab897941f2a5100474720c832901ecc9a2c71886 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 20 Jan 2015 12:02:20 -0500 Subject: [PATCH 0231/1650] Fix typo and consolidate curtsies.py --- bpython/curtsies.py | 25 ++++++++++++------------- bpython/curtsiesfrontend/events.py | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 953fcafb7..1dc7b1ac1 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -107,18 +107,6 @@ def after_suspend(): after_suspend=after_suspend) as repl: repl.height, repl.width = window.t.height, window.t.width - if interactive: - # Add custom help command - # TODO: add methods to run the code - repl.coderunner.interp.locals['_repl'] = repl - - repl.coderunner.interp.runsource( - 'from bpython.curtsiesfrontend._internal import _Helper') - repl.coderunner.interp.runsource('help = _Helper(_repl)\n') - - del repl.coderunner.interp.locals['_repl'] - del repl.coderunner.interp.locals['_Helper'] - def process_event(e): """If None is passed in, just paint the screen""" try: @@ -134,8 +122,19 @@ def process_event(e): scrolled = window.render_to_terminal(array, cursor_pos) repl.scroll_offset += scrolled - # run startup file if interactive: + # Add custom help command + # TODO: add methods to run the code + repl.coderunner.interp.locals['_repl'] = repl + + repl.coderunner.interp.runsource( + 'from bpython.curtsiesfrontend._internal import _Helper') + repl.coderunner.interp.runsource('help = _Helper(_repl)\n') + + del repl.coderunner.interp.locals['_repl'] + del repl.coderunner.interp.locals['_Helper'] + + # run startup file process_event(bpythonevents.RunStartupFileEvent()) # handle paste diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index 3b2b3c755..84ca6a307 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -36,4 +36,4 @@ def __repr__(self): (self.who, self.when - time.time())) class RunStartupFileEvent(curtsies.events.Event): - """Reqeust to run the startup file.""" + """Request to run the startup file.""" From 8b5df4f17023e6dc2cb87c16054fbc3d089301c8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 15 Jan 2015 22:47:18 +0100 Subject: [PATCH 0232/1650] Make string operations more readable (fixes #439) Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/replpainter.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 299287070..c4812dd43 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -152,12 +152,23 @@ def paint_infobox(rows, columns, matches, argspec, match, docstring, config, for (matches_lines(rows, width, matches, match, config, format) if matches else []) + (formatted_docstring(docstring, width, config) if docstring else [])) - output_lines = [] + def add_border(line): + """Add colored borders left and right to a line.""" + new_line = border_color(config.left_border + ' ') + new_line += line.ljust(width)[:width] + new_line += border_color(' ' + config.right_border) + return new_line + border_color = func_for_letter(config.color_scheme['main']) - output_lines.append(border_color(config.left_top_corner+config.top_border*(width+2)+config.right_top_corner)) - for line in lines: - output_lines.append(border_color(config.left_border+u' ')+((line+' '*(width - len(line)))[:width])+border_color(u' ' + config.right_border)) - output_lines.append(border_color(config.left_bottom_corner+config.bottom_border*(width+2)+config.right_bottom_corner)) + + top_line = border_color(config.left_top_corner + + config.top_border * (width + 2) + + config.right_top_corner) + bottom_line = border_color(config.left_bottom_corner + + config.bottom_border * (width + 2) + + config.right_bottom_corner) + + output_lines = [top_line] + map(add_border, lines) + [bottom_line] r = fsarray(output_lines[:min(rows-1, len(output_lines)-1)] + output_lines[-1:]) return r From 7392fb165ffb1534d5687cda19d5448816251c45 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 01:45:30 +0100 Subject: [PATCH 0233/1650] Bump curtsies dependency Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index e19daaf72..54fd66afc 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -6,7 +6,7 @@ pip install setuptools if [[ $RUN == nosetests ]]; then # core dependencies - pip install pygments requests 'curtsies >=0.1.16,<0.2.0' greenlet + pip install pygments requests 'curtsies >=0.1.17,<0.2.0' greenlet # filewatch specific dependencies pip install watchdog # translation specific dependencies diff --git a/setup.py b/setup.py index f3118008a..836e0eed7 100755 --- a/setup.py +++ b/setup.py @@ -168,7 +168,7 @@ def initialize_options(self): install_requires = [ 'pygments', 'requests', - 'curtsies >=0.1.16, <0.2.0', + 'curtsies >=0.1.17, <0.2.0', 'greenlet' ] From 2ca120e3ea6042b6de1fe93936b249a2aace9a3f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 09:16:21 +0100 Subject: [PATCH 0234/1650] Check if default conf contains duplicate keys Signed-off-by: Sebastian Ramacher --- bpython/test/test_config.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index 23db0c645..61e05bf7e 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -34,6 +34,16 @@ def test_load_theme(self): config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, defaults) self.assertEquals(struct.color_scheme, expected) + def test_keybindings_default_contains_no_duplicates(self): + struct = self.load_temp_config("") + + keys = (attr for attr in dir(struct) if attr.endswith('_key')) + mapped_keys = [getattr(struct, key) for key in keys if + getattr(struct, key)] + + mapped_keys_set = set(mapped_keys) + self.assertEqual(len(mapped_keys), len(mapped_keys_set)) + def test_keybindings_use_default(self): struct = self.load_temp_config(textwrap.dedent(""" [keyboard] From 305b44577ab6f1033c1b0d953296558788e570be Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 09:26:16 +0100 Subject: [PATCH 0235/1650] Check if unbinding works Signed-off-by: Sebastian Ramacher --- bpython/config.py | 3 ++- bpython/test/test_config.py | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index b17cfa047..e9bbe2d3e 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -194,7 +194,8 @@ def get_key_no_doublebind(attr, already_used={}): struct.save_append_py = config.getboolean('general', 'save_append_py') struct.curtsies_list_above = config.getboolean('curtsies', 'list_above') - struct.curtsies_right_arrow_completion = config.getboolean('curtsies', 'right_arrow_completion') + struct.curtsies_right_arrow_completion = config.getboolean('curtsies', + 'right_arrow_completion') color_scheme_name = config.get('general', 'color_scheme') diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index 61e05bf7e..cd51af799 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -71,3 +71,11 @@ def test_keybindings_use_other_default_issue_447(self): self.assertEqual(struct.help_key, 'F2') self.assertEqual(struct.show_source_key, 'F9') + def test_keybindings_unset(self): + struct = self.load_temp_config(textwrap.dedent(""" + [keyboard] + help = + """)) + + self.assertFalse(struct.help_key) + From 0badec0509b66b3b54833a23317d0de4299c1a05 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 16:08:31 +0100 Subject: [PATCH 0236/1650] Newlines Signed-off-by: Sebastian Ramacher --- bpython/test/test_importcompletion.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 706be7bdb..b5f73512d 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -3,32 +3,40 @@ import unittest class TestSimpleComplete(unittest.TestCase): + def setUp(self): self.original_modules = importcompletion.modules importcompletion.modules = ['zzabc', 'zzabd', 'zzefg', 'zzabc.e', 'zzabc.f'] + def tearDown(self): importcompletion.modules = self.original_modules + def test_simple_completion(self): self.assertEqual(importcompletion.complete(10, 'import zza'), ['zzabc', 'zzabd']) + def test_package_completion(self): self.assertEqual(importcompletion.complete(13, 'import zzabc.'), ['zzabc.e', 'zzabc.f', ]) class TestRealComplete(unittest.TestCase): + @classmethod def setUpClass(cls): [_ for _ in importcompletion.find_iterator] __import__('sys') __import__('os') + @classmethod def tearDownClass(cls): importcompletion.find_iterator = importcompletion.find_all_modules() importcompletion.modules = set() + def test_from_attribute(self): self.assertEqual(importcompletion.complete(19, 'from sys import arg'), ['argv']) + def test_from_attr_module(self): self.assertEqual(importcompletion.complete(9, 'from os.p'), ['os.path']) + def test_from_package(self): self.assertEqual(importcompletion.complete(17, 'from xml import d'), ['dom']) - From b5a2d7c06f1496d61766a5dcdd1ffb2ce36d840f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 16:39:49 +0100 Subject: [PATCH 0237/1650] Clean up imports Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 621be7e45..a515f7f87 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -1,6 +1,6 @@ # The MIT License # -# Copyright (c) 2009-2012 the bpython authors. +# Copyright (c) 2009-2015 the bpython authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -21,18 +21,17 @@ # THE SOFTWARE. # -from __future__ import with_statement import __builtin__ import __main__ import abc import rlcompleter -import line as lineparts import re import os from glob import glob from functools import partial from bpython import inspection from bpython import importcompletion +from bpython import line as lineparts from bpython._py3compat import py3 # Autocomplete modes From b44d1855b0d93ab711038a9e500f4082b7058589 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 16:45:56 +0100 Subject: [PATCH 0238/1650] mode is no longer used Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a515f7f87..ba775ee22 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -66,7 +66,6 @@ def get_completer(completers, cursor_offset, line, **kwargs): the cursor is current_block is the possibly multiline not-yet-evaluated block of code which the current line is part of - mode is one of SIMPLE, SUBSTRING or FUZZY - ways to find matches complete_magic_methods is a bool of whether we ought to complete double underscore methods like __len__ in method signatures """ From f357e4c8a7b8ddc19614291891be07d37833e399 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 17:35:50 +0100 Subject: [PATCH 0239/1650] Make MAGIC_METHODS immutable Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index ba775ee22..aaf0fe647 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -41,13 +41,13 @@ ALL_MODES = (SIMPLE, SUBSTRING, FUZZY) -MAGIC_METHODS = ["__%s__" % s for s in [ +MAGIC_METHODS = ("__%s__" % s for s in ( "init", "repr", "str", "lt", "le", "eq", "ne", "gt", "ge", "cmp", "hash", "nonzero", "unicode", "getattr", "setattr", "get", "set", "call", "len", "getitem", "setitem", "iter", "reversed", "contains", "add", "sub", "mul", "floordiv", "mod", "divmod", "pow", "lshift", "rshift", "and", "xor", "or", "div", "truediv", "neg", "pos", "abs", "invert", "complex", "int", "float", - "oct", "hex", "index", "coerce", "enter", "exit"]] + "oct", "hex", "index", "coerce", "enter", "exit")) def after_last_dot(name): From adb00a09c14afdfa33f36a1fad858b9358c98add Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 17:56:12 +0100 Subject: [PATCH 0240/1650] Be consistent with completion classes Currently there is a mix of classes that are used with staticmethods and classmethods, and others that need to be instantiated. This unifies the use of all these classes. Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 209 +++++++++++++++++------------ bpython/test/test_autocomplete.py | 25 ++-- bpython/test/test_curtsies_repl.py | 8 +- 3 files changed, 141 insertions(+), 101 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index aaf0fe647..f94c57085 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -53,44 +53,14 @@ def after_last_dot(name): return name.rstrip('.').rsplit('.')[-1] -def get_completer(completers, cursor_offset, line, **kwargs): - """Returns a list of matches and an applicable completer - - If no matches available, returns a tuple of an empty list and None - - kwargs (all required): - cursor_offset is the current cursor column - line is a string of the current line - locals_ is a dictionary of the environment - argspec is an inspect.ArgSpec instance for the current function where - the cursor is - current_block is the possibly multiline not-yet-evaluated block of - code which the current line is part of - complete_magic_methods is a bool of whether we ought to complete - double underscore methods like __len__ in method signatures - """ - - for completer in completers: - matches = completer.matches(cursor_offset, line, **kwargs) - if matches is not None: - return matches, (completer if matches else None) - return [], None - -def get_completer_bpython(**kwargs): - """""" - return get_completer([DictKeyCompletion, - StringLiteralAttrCompletion, - ImportCompletion, - FilenameCompletion, - MagicMethodCompletion, - GlobalCompletion, - CumulativeCompleter([AttrCompletion, ParameterNameCompletion])], - **kwargs) class BaseCompletionType(object): """Describes different completion types""" - @classmethod - def matches(cls, cursor_offset, line, **kwargs): + + def __init__(self, shown_before_tab=True): + self._shown_before_tab = shown_before_tab + + def matches(self, cursor_offset, line, **kwargs): """Returns a list of possible matches given a line and cursor, or None if this completion type isn't applicable. @@ -105,37 +75,42 @@ def matches(cls, cursor_offset, line, **kwargs): * `substitute(cur, line, match)` in a match for what's found with `target` """ raise NotImplementedError - @classmethod - def locate(cls, cursor_offset, line): + + def locate(self, cursor_offset, line): """Returns a start, stop, and word given a line and cursor, or None if no target for this type of completion is found under the cursor""" raise NotImplementedError - @classmethod - def format(cls, word): + + def format(self, word): return word - shown_before_tab = True # whether suggestions should be shown before the - # user hits tab, or only once that has happened - def substitute(cls, cursor_offset, line, match): + + def substitute(self, cursor_offset, line, match): """Returns a cursor offset and line with match swapped in""" - start, end, word = cls.locate(cursor_offset, line) + start, end, word = self.locate(cursor_offset, line) result = start + len(match), line[:start] + match + line[end:] return result -class CumulativeCompleter(object): + @property + def shown_before_tab(self): + """Whether suggestions should be shown before the user hits tab, or only + once that has happened.""" + return self._shown_before_tab + +class CumulativeCompleter(BaseCompletionType): """Returns combined matches from several completers""" + def __init__(self, completers): if not completers: raise ValueError("CumulativeCompleter requires at least one completer") self._completers = completers - self.shown_before_tab = True - @property - def locate(self): - return self._completers[0].locate if self._completers else lambda *args: None + super(CumulativeCompleter, self).__init__(True) - @property - def format(self): - return self._completers[0].format if self._completers else lambda s: s + def locate(self, current_offset, line): + return self._completers[0].locate(current_offset, line) + + def format(self, word): + return self._completers[0].format(word) def matches(self, cursor_offset, line, locals_, argspec, current_block, complete_magic_methods): all_matches = [] @@ -155,16 +130,22 @@ def matches(self, cursor_offset, line, locals_, argspec, current_block, complete class ImportCompletion(BaseCompletionType): - @classmethod - def matches(cls, cursor_offset, line, **kwargs): + + def matches(self, cursor_offset, line, **kwargs): return importcompletion.complete(cursor_offset, line) - locate = staticmethod(lineparts.current_word) - format = staticmethod(after_last_dot) + + def locate(self, current_offset, line): + return lineparts.current_word(current_offset, line) + + def format(self, word): + return after_last_dot(word) class FilenameCompletion(BaseCompletionType): - shown_before_tab = False - @classmethod - def matches(cls, cursor_offset, line, **kwargs): + + def __init__(self): + super(FilenameCompletion, self).__init__(False) + + def matches(self, cursor_offset, line, **kwargs): cs = lineparts.current_string(cursor_offset, line) if cs is None: return None @@ -180,9 +161,10 @@ def matches(cls, cursor_offset, line, **kwargs): matches.append(filename) return matches - locate = staticmethod(lineparts.current_string) - @classmethod - def format(cls, filename): + def locate(self, current_offset, line): + return lineparts.current_string(current_offset, line) + + def format(self, filename): filename.rstrip(os.sep).rsplit(os.sep)[-1] if os.sep in filename[:-1]: return filename[filename.rindex(os.sep, 0, -1)+1:] @@ -190,9 +172,9 @@ def format(cls, filename): return filename class AttrCompletion(BaseCompletionType): - @classmethod - def matches(cls, cursor_offset, line, locals_, **kwargs): - r = cls.locate(cursor_offset, line) + + def matches(self, cursor_offset, line, locals_, **kwargs): + r = self.locate(cursor_offset, line) if r is None: return None text = r[2] @@ -217,14 +199,16 @@ def matches(cls, cursor_offset, line, locals_, **kwargs): if not match.split('.')[-1].startswith('_')] return matches - locate = staticmethod(lineparts.current_dotted_attribute) - format = staticmethod(after_last_dot) + def locate(self, current_offset, line): + return lineparts.current_dotted_attribute(current_offset, line) + + def format(self, word): + return after_last_dot(word) class DictKeyCompletion(BaseCompletionType): - locate = staticmethod(lineparts.current_dict_key) - @classmethod - def matches(cls, cursor_offset, line, locals_, **kwargs): - r = cls.locate(cursor_offset, line) + + def matches(self, cursor_offset, line, locals_, **kwargs): + r = self.locate(cursor_offset, line) if r is None: return None start, end, orig = r @@ -237,15 +221,17 @@ def matches(cls, cursor_offset, line, locals_, **kwargs): return ["{!r}]".format(k) for k in obj.keys() if repr(k).startswith(orig)] else: return [] - @classmethod - def format(cls, match): + + def locate(self, current_offset, line): + return lineparts.current_dict_key(current_offset, line) + + def format(self, match): return match[:-1] class MagicMethodCompletion(BaseCompletionType): - locate = staticmethod(lineparts.current_method_definition_name) - @classmethod - def matches(cls, cursor_offset, line, current_block, **kwargs): - r = cls.locate(cursor_offset, line) + + def matches(self, cursor_offset, line, current_block, **kwargs): + r = self.locate(cursor_offset, line) if r is None: return None if 'class' not in current_block: @@ -253,14 +239,17 @@ def matches(cls, cursor_offset, line, current_block, **kwargs): start, end, word = r return [name for name in MAGIC_METHODS if name.startswith(word)] + def locate(self, current_offset, line): + return lineparts.current_method_definition_name(current_offset, line) + class GlobalCompletion(BaseCompletionType): - @classmethod - def matches(cls, cursor_offset, line, locals_, **kwargs): + + def matches(self, cursor_offset, line, locals_, **kwargs): """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace that match. """ - r = cls.locate(cursor_offset, line) + r = self.locate(cursor_offset, line) if r is None: return None start, end, text = r @@ -279,14 +268,15 @@ def matches(cls, cursor_offset, line, locals_, **kwargs): matches.sort() return matches - locate = staticmethod(lineparts.current_single_word) + def locate(self, current_offset, line): + return lineparts.current_single_word(current_offset, line) class ParameterNameCompletion(BaseCompletionType): - @classmethod - def matches(cls, cursor_offset, line, argspec, **kwargs): + + def matches(self, cursor_offset, line, argspec, **kwargs): if not argspec: return None - r = cls.locate(cursor_offset, line) + r = self.locate(cursor_offset, line) if r is None: return None start, end, word = r @@ -297,13 +287,14 @@ def matches(cls, cursor_offset, line, argspec, **kwargs): matches.extend(name + '=' for name in argspec[1][4] if name.startswith(word)) return matches - locate = staticmethod(lineparts.current_word) + + def locate(self, current_offset, line): + return lineparts.current_word(current_offset, line) class StringLiteralAttrCompletion(BaseCompletionType): - locate = staticmethod(lineparts.current_string_literal_attr) - @classmethod - def matches(cls, cursor_offset, line, **kwargs): - r = cls.locate(cursor_offset, line) + + def matches(self, cursor_offset, line, **kwargs): + r = self.locate(cursor_offset, line) if r is None: return None start, end, word = r @@ -313,6 +304,48 @@ def matches(cls, cursor_offset, line, **kwargs): return [match for match in matches if not match.startswith('_')] return matches + def locate(self, current_offset, line): + return lineparts.current_string_literal_attr(current_offset, line) + + +def get_completer(completers, cursor_offset, line, **kwargs): + """Returns a list of matches and an applicable completer + + If no matches available, returns a tuple of an empty list and None + + kwargs (all required): + cursor_offset is the current cursor column + line is a string of the current line + locals_ is a dictionary of the environment + argspec is an inspect.ArgSpec instance for the current function where + the cursor is + current_block is the possibly multiline not-yet-evaluated block of + code which the current line is part of + complete_magic_methods is a bool of whether we ought to complete + double underscore methods like __len__ in method signatures + """ + + for completer in completers: + matches = completer.matches(cursor_offset, line, **kwargs) + if matches is not None: + return matches, (completer if matches else None) + return [], None + +BPYTHON_COMPLETER = ( + DictKeyCompletion(), + StringLiteralAttrCompletion(), + ImportCompletion(), + FilenameCompletion(), + MagicMethodCompletion(), + GlobalCompletion(), + CumulativeCompleter((AttrCompletion(), ParameterNameCompletion())) +) + +def get_completer_bpython(**kwargs): + """""" + return get_completer(BPYTHON_COMPLETER, + **kwargs) + class EvaluationError(Exception): """Raised if an exception occurred in safe_eval.""" diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 962db30e2..99f5b09df 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -20,7 +20,8 @@ def test_catches_syntax_error(self): class TestFormatters(unittest.TestCase): def test_filename(self): - last_part_of_filename = autocomplete.FilenameCompletion.format + completer = autocomplete.FilenameCompletion() + last_part_of_filename = completer.format self.assertEqual(last_part_of_filename('abc'), 'abc') self.assertEqual(last_part_of_filename('abc/'), 'abc/') self.assertEqual(last_part_of_filename('abc/efg'), 'efg') @@ -98,42 +99,46 @@ def test_two_completers_get_both(self): class TestFilenameCompletion(unittest.TestCase): + def setUp(self): + self.completer = autocomplete.FilenameCompletion() + def test_locate_fails_when_not_in_string(self): - self.assertEqual(autocomplete.FilenameCompletion.locate(4, "abcd"), None) + self.assertEqual(self.completer.locate(4, "abcd"), None) def test_locate_succeeds_when_in_string(self): - self.assertEqual(autocomplete.FilenameCompletion.locate(4, "a'bc'd"), (2, 4, 'bc')) + self.assertEqual(self.completer.locate(4, "a'bc'd"), (2, 4, 'bc')) @mock.patch('bpython.autocomplete.glob', new=lambda text: []) def test_match_returns_none_if_not_in_string(self): - self.assertEqual(autocomplete.FilenameCompletion.matches(2, 'abcd'), None) + self.assertEqual(self.completer.matches(2, 'abcd'), None) @mock.patch('bpython.autocomplete.glob', new=lambda text: []) def test_match_returns_empty_list_when_no_files(self): - self.assertEqual(autocomplete.FilenameCompletion.matches(2, '"a'), []) + self.assertEqual(self.completer.matches(2, '"a'), []) @mock.patch('bpython.autocomplete.glob', new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: False) @mock.patch('os.path.sep', new='/') def test_match_returns_files_when_files_exist(self): - self.assertEqual(autocomplete.FilenameCompletion.matches(2, '"x'), ['abcde', 'aaaaa']) + self.assertEqual(self.completer.matches(2, '"x'), ['abcde', 'aaaaa']) @mock.patch('bpython.autocomplete.glob', new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: True) @mock.patch('os.path.sep', new='/') def test_match_returns_dirs_when_dirs_exist(self): - self.assertEqual(autocomplete.FilenameCompletion.matches(2, '"x'), ['abcde/', 'aaaaa/']) + self.assertEqual(self.completer.matches(2, '"x'), ['abcde/', 'aaaaa/']) @mock.patch('bpython.autocomplete.glob', new=lambda text: ['/expand/ed/abcde', '/expand/ed/aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text.replace('~', '/expand/ed')) @mock.patch('os.path.isdir', new=lambda text: False) @mock.patch('os.path.sep', new='/') def test_tilde_stays_pretty(self): - self.assertEqual(autocomplete.FilenameCompletion.matches(4, '"~/a'), ['~/abcde', '~/aaaaa']) + self.assertEqual(self.completer.matches(4, '"~/a'), ['~/abcde', '~/aaaaa']) @mock.patch('os.path.sep', new='/') def test_formatting_takes_just_last_part(self): - self.assertEqual(autocomplete.FilenameCompletion.format('/hello/there/'), 'there/') - self.assertEqual(autocomplete.FilenameCompletion.format('/hello/there'), 'there') + self.assertEqual(self.completer.format('/hello/there/'), 'there/') + self.assertEqual(self.completer.format('/hello/there'), 'there') + diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 184ac5125..762f31ab1 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -161,7 +161,8 @@ def test_list_win_visible_and_match_selected_on_tab_when_multiple_options(self): self.repl._current_line = " './'" self.repl._cursor_offset = 2 with patch('bpython.autocomplete.get_completer_bpython') as mock: - mock.return_value = (['./abc', './abcd', './bcd'], autocomplete.FilenameCompletion) + mock.return_value = (['./abc', './abcd', './bcd'], + autocomplete.FilenameCompletion()) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() @@ -172,7 +173,8 @@ def test_list_win_not_visible_and_cseq_if_cseq(self): self.repl._current_line = " './a'" self.repl._cursor_offset = 5 with patch('bpython.autocomplete.get_completer_bpython') as mock: - mock.return_value = (['./abcd', './abce'], autocomplete.FilenameCompletion) + mock.return_value = (['./abcd', './abce'], + autocomplete.FilenameCompletion()) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() @@ -184,7 +186,7 @@ def test_list_win_not_visible_and_match_selected_if_one_option(self): self.repl._current_line = " './a'" self.repl._cursor_offset = 5 with patch('bpython.autocomplete.get_completer_bpython') as mock: - mock.return_value = (['./abcd'], autocomplete.FilenameCompletion) + mock.return_value = (['./abcd'], autocomplete.FilenameCompletion()) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() From e90b1217f5a6ca8108701c60cb4c0c446c5d228a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 18:11:48 +0100 Subject: [PATCH 0241/1650] Remove unused import Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index f94c57085..6345119cb 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -28,7 +28,6 @@ import re import os from glob import glob -from functools import partial from bpython import inspection from bpython import importcompletion from bpython import line as lineparts From 5809caefdf431f873ce511ce97045e2ca155c188 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 18:19:03 +0100 Subject: [PATCH 0242/1650] Use a set directly Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 6345119cb..330279818 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -112,7 +112,7 @@ def format(self, word): return self._completers[0].format(word) def matches(self, cursor_offset, line, locals_, argspec, current_block, complete_magic_methods): - all_matches = [] + all_matches = set() for completer in self._completers: # these have to be explicitely listed to deal with the different # signatures of various matches() methods of completers @@ -123,9 +123,9 @@ def matches(self, cursor_offset, line, locals_, argspec, current_block, complete current_block=current_block, complete_magic_methods=complete_magic_methods) if matches is not None: - all_matches.extend(matches) + all_matches.update(matches) - return sorted(set(all_matches)) + return sorted(all_matches) class ImportCompletion(BaseCompletionType): From 51dbf41437366d4ae6719c43315a31eaf8d389b9 Mon Sep 17 00:00:00 2001 From: Keyan Pishdadian Date: Tue, 20 Jan 2015 20:57:58 -0500 Subject: [PATCH 0243/1650] Refactor and fix keybinding issue (fixes #447) This changes the logic for the function get_key_no_doublebind() and removes the mutable list which was previously used to keep track of keybindings which were already used. Although this fix passes the test cases and the specific case mentioned in #447, the behavior is such that specifying the same custom keybinding for two commands will result in only one command being bound. --- bpython/config.py | 55 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index e9bbe2d3e..3cef922f8 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -104,6 +104,39 @@ def loadini(struct, configfile): 'list_above' : False, 'right_arrow_completion' : True, }} + + default_keys_to_commands = { + '': 'exit', + 'C-n': 'down_one_line', + 'C-l': 'clear_screen', + 'C-k': 'kill_line', + 'C-o': 'search', + 'C-h': 'backspace', + 'C-f': 'right', + 'C-e': 'end_of_line', + 'C-d': 'delete', + 'C-b':'left', + 'C-a': 'beginning_of_line', + 'C-z': 'suspend', + 'C-y': 'yank_from_buffer', + 'C-x': 'edit_current_block', + 'C-w': 'clear_word', + 'C-u': 'clear_line', + 'C-t': 'transpose_chars', + 'C-s': 'save', + 'C-r': 'undo', + 'C-p': 'up_one_line', + 'F10': 'copy_clipboard', + 'F1': 'help', + 'F2': 'show_source', + 'F3': 'edit_config', + 'F5': 'toggle_file_watch', + 'F6': 'reimport', + 'F7': 'external_editor', + 'F8': 'pastebin', + 'F9': 'last_output' + } + fill_config_with_default_values(config, defaults) if not config.read(config_path): # No config file. If the user has it in the old place then complain @@ -113,17 +146,17 @@ def loadini(struct, configfile): "%s\n" % default_config_path()) sys.exit(1) - def get_key_no_doublebind(attr, already_used={}): - """Clears any other configured keybindings using this key""" - key = config.get('keyboard', attr) - if key in already_used: - default = defaults['keyboard'][already_used[key]] - if default in already_used: - setattr(struct, '%s_key' % already_used[key], '') - else: - setattr(struct, '%s_key' % already_used[key], default) - already_used[key] = attr - return key + + def get_key_no_doublebind(command): + default_commands_to_keys = defaults['keyboard'] + requested_key = config.get('keyboard', command) + default_command = default_keys_to_commands[requested_key] + + if default_commands_to_keys[default_command] == \ + config.get('keyboard', default_command): + setattr(struct, '%s_key' % default_command, '') + + return requested_key struct.config_path = config_path From 6a29a44ab7e3fa62febd2cc58caef9d4861f26b7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 20:05:56 +0100 Subject: [PATCH 0244/1650] Move keywords import --- bpython/autocomplete.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 330279818..640de9f6a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -24,10 +24,13 @@ import __builtin__ import __main__ import abc -import rlcompleter -import re +import keyword import os +import re +import rlcompleter + from glob import glob + from bpython import inspection from bpython import importcompletion from bpython import line as lineparts @@ -255,7 +258,6 @@ def matches(self, cursor_offset, line, locals_, **kwargs): hash = {} n = len(text) - import keyword for word in keyword.kwlist: if method_match(word, n, text): hash[word] = 1 From 64dd9de15de8a4b1af65b248855ead5ba4a56b2d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 20:06:27 +0100 Subject: [PATCH 0245/1650] Use sets everywhere MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … and make sure autocompletion results are sorted Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 42 +++++++++++++++---------------- bpython/test/test_autocomplete.py | 11 +++++--- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 640de9f6a..dda294451 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -152,7 +152,7 @@ def matches(self, cursor_offset, line, **kwargs): if cs is None: return None start, end, text = cs - matches = [] + matches = set() username = text.split(os.path.sep, 1)[0] user_dir = os.path.expanduser(username) for filename in glob(os.path.expanduser(text + '*')): @@ -160,7 +160,7 @@ def matches(self, cursor_offset, line, **kwargs): filename += os.path.sep if text.startswith('~'): filename = username + filename[len(user_dir):] - matches.append(filename) + matches.add(filename) return matches def locate(self, current_offset, line): @@ -191,14 +191,14 @@ def matches(self, cursor_offset, line, locals_, **kwargs): i -= 1 break methodtext = text[-i:] - matches = [''.join([text[:-i], m]) for m in - attr_matches(methodtext, locals_)] + matches = set(''.join([text[:-i], m]) + for m in attr_matches(methodtext, locals_)) #TODO add open paren for methods via _callable_prefix (or decide not to) # unless the first character is a _ filter out all attributes starting with a _ if not text.split('.')[-1].startswith('_'): - matches = [match for match in matches - if not match.split('.')[-1].startswith('_')] + matches = set(match for match in matches + if not match.split('.')[-1].startswith('_')) return matches def locate(self, current_offset, line): @@ -218,11 +218,12 @@ def matches(self, cursor_offset, line, locals_, **kwargs): try: obj = safe_eval(dexpr, locals_) except EvaluationError: - return [] + return set() if obj and isinstance(obj, type({})) and obj.keys(): - return ["{!r}]".format(k) for k in obj.keys() if repr(k).startswith(orig)] + return set("{!r}".format(k) for k in obj.keys() + if repr(k).startswith(orig)) else: - return [] + return set() def locate(self, current_offset, line): return lineparts.current_dict_key(current_offset, line) @@ -239,7 +240,7 @@ def matches(self, cursor_offset, line, current_block, **kwargs): if 'class' not in current_block: return None start, end, word = r - return [name for name in MAGIC_METHODS if name.startswith(word)] + return set(name for name in MAGIC_METHODS if name.startswith(word)) def locate(self, current_offset, line): return lineparts.current_method_definition_name(current_offset, line) @@ -256,17 +257,15 @@ def matches(self, cursor_offset, line, locals_, **kwargs): return None start, end, text = r - hash = {} + matches = set() n = len(text) for word in keyword.kwlist: if method_match(word, n, text): - hash[word] = 1 + matches.add(word) for nspace in [__builtin__.__dict__, locals_]: for word, val in nspace.items(): if method_match(word, len(text), text) and word != "__builtins__": - hash[_callable_postfix(val, word)] = 1 - matches = hash.keys() - matches.sort() + matches.add(_callable_postfix(val, word)) return matches def locate(self, current_offset, line): @@ -282,10 +281,11 @@ def matches(self, cursor_offset, line, argspec, **kwargs): return None start, end, word = r if argspec: - matches = [name + '=' for name in argspec[1][0] - if isinstance(name, basestring) and name.startswith(word)] + matches = set(name + '=' for name in argspec[1][0] + if isinstance(name, basestring) and + name.startswith(word)) if py3: - matches.extend(name + '=' for name in argspec[1][4] + matches.update(name + '=' for name in argspec[1][4] if name.startswith(word)) return matches @@ -300,9 +300,9 @@ def matches(self, cursor_offset, line, **kwargs): return None start, end, word = r attrs = dir('') - matches = [att for att in attrs if att.startswith(word)] + matches = set(att for att in attrs if att.startswith(word)) if not word.startswith('_'): - return [match for match in matches if not match.startswith('_')] + return set(match for match in matches if not match.startswith('_')) return matches def locate(self, current_offset, line): @@ -329,7 +329,7 @@ def get_completer(completers, cursor_offset, line, **kwargs): for completer in completers: matches = completer.matches(cursor_offset, line, **kwargs) if matches is not None: - return matches, (completer if matches else None) + return sorted(matches), (completer if matches else None) return [], None BPYTHON_COMPLETER = ( diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 99f5b09df..3ac4085d5 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -114,28 +114,31 @@ def test_match_returns_none_if_not_in_string(self): @mock.patch('bpython.autocomplete.glob', new=lambda text: []) def test_match_returns_empty_list_when_no_files(self): - self.assertEqual(self.completer.matches(2, '"a'), []) + self.assertEqual(self.completer.matches(2, '"a'), set()) @mock.patch('bpython.autocomplete.glob', new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: False) @mock.patch('os.path.sep', new='/') def test_match_returns_files_when_files_exist(self): - self.assertEqual(self.completer.matches(2, '"x'), ['abcde', 'aaaaa']) + self.assertEqual(sorted(self.completer.matches(2, '"x')), + ['aaaaa', 'abcde']) @mock.patch('bpython.autocomplete.glob', new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: True) @mock.patch('os.path.sep', new='/') def test_match_returns_dirs_when_dirs_exist(self): - self.assertEqual(self.completer.matches(2, '"x'), ['abcde/', 'aaaaa/']) + self.assertEqual(sorted(self.completer.matches(2, '"x')), + ['aaaaa/', 'abcde/']) @mock.patch('bpython.autocomplete.glob', new=lambda text: ['/expand/ed/abcde', '/expand/ed/aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text.replace('~', '/expand/ed')) @mock.patch('os.path.isdir', new=lambda text: False) @mock.patch('os.path.sep', new='/') def test_tilde_stays_pretty(self): - self.assertEqual(self.completer.matches(4, '"~/a'), ['~/abcde', '~/aaaaa']) + self.assertEqual(sorted(self.completer.matches(4, '"~/a')), + ['~/aaaaa', '~/abcde']) @mock.patch('os.path.sep', new='/') def test_formatting_takes_just_last_part(self): From 4dbd59abe1865711c9de244344d7a101bd1ea0dd Mon Sep 17 00:00:00 2001 From: Keyan Pishdadian Date: Wed, 21 Jan 2015 14:22:53 -0500 Subject: [PATCH 0246/1650] Make default keybinding reverse dictionary to interable --- bpython/config.py | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 3cef922f8..f5d22469a 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -105,37 +105,8 @@ def loadini(struct, configfile): 'right_arrow_completion' : True, }} - default_keys_to_commands = { - '': 'exit', - 'C-n': 'down_one_line', - 'C-l': 'clear_screen', - 'C-k': 'kill_line', - 'C-o': 'search', - 'C-h': 'backspace', - 'C-f': 'right', - 'C-e': 'end_of_line', - 'C-d': 'delete', - 'C-b':'left', - 'C-a': 'beginning_of_line', - 'C-z': 'suspend', - 'C-y': 'yank_from_buffer', - 'C-x': 'edit_current_block', - 'C-w': 'clear_word', - 'C-u': 'clear_line', - 'C-t': 'transpose_chars', - 'C-s': 'save', - 'C-r': 'undo', - 'C-p': 'up_one_line', - 'F10': 'copy_clipboard', - 'F1': 'help', - 'F2': 'show_source', - 'F3': 'edit_config', - 'F5': 'toggle_file_watch', - 'F6': 'reimport', - 'F7': 'external_editor', - 'F8': 'pastebin', - 'F9': 'last_output' - } + default_keys_to_commands = dict((value, key) for (key, value) + in defaults['keyboard'].iteritems()) fill_config_with_default_values(config, defaults) if not config.read(config_path): From a194976998c9eeac0f9e71fa14bca57a909a4237 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 21:30:50 +0100 Subject: [PATCH 0247/1650] Use sets in bpython.importcompletion Also avoid multiple evaluation of the same input Signed-off-by: Sebastian Ramacher --- bpython/importcompletion.py | 51 +++++++++++++++------------ bpython/test/test_importcompletion.py | 15 +++++--- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index da08704be..486075774 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -59,11 +59,11 @@ def catch_warnings(): def module_matches(cw, prefix=''): """Modules names to replace cw with""" full = '%s.%s' % (prefix, cw) if prefix else cw - matches = [name for name in modules - if (name.startswith(full) and - name.find('.', len(full)) == -1)] + matches = set(name for name in modules + if (name.startswith(full) and + name.find('.', len(full)) == -1)) if prefix: - return [match[len(prefix)+1:] for match in matches] + return set(match[len(prefix)+1:] for match in matches) else: return matches @@ -72,19 +72,20 @@ def attr_matches(cw, prefix='', only_modules=False): full = '%s.%s' % (prefix, cw) if prefix else cw module_name, _, name_after_dot = full.rpartition('.') if module_name not in sys.modules: - return [] + return set() module = sys.modules[module_name] if only_modules: - matches = [name for name in dir(module) - if name.startswith(name_after_dot) and - '%s.%s' % (module_name, name) in sys.modules] + matches = set(name for name in dir(module) + if (name.startswith(name_after_dot) and + '%s.%s' % (module_name, name)) in sys.modules) else: - matches = [name for name in dir(module) if name.startswith(name_after_dot)] + matches = set(name for name in dir(module) + if name.startswith(name_after_dot)) module_part, _, _ = cw.rpartition('.') if module_part: - return ['%s.%s' % (module_part, m) for m in matches] - return matches + matches = set('%s.%s' % (module_part, m) for m in matches) + return matches def module_attr_matches(name): """Only attributes which are modules to replace name with""" return attr_matches(name, prefix='', only_modules=True) @@ -99,21 +100,27 @@ def complete(cursor_offset, line): if result is None: return None - if lineparts.current_from_import_from(cursor_offset, line) is not None: - if lineparts.current_from_import_import(cursor_offset, line) is not None: + from_import_from = lineparts.current_from_import_from(cursor_offset, line) + if from_import_from is not None: + from_import_import = lineparts.current_from_import_import(cursor_offset, + line) + if from_import_import is not None: # `from a import ` completion - return (module_matches(lineparts.current_from_import_import(cursor_offset, line)[2], - lineparts.current_from_import_from(cursor_offset, line)[2]) + - attr_matches(lineparts.current_from_import_import(cursor_offset, line)[2], - lineparts.current_from_import_from(cursor_offset, line)[2])) + matches = module_matches(from_import_import[2], from_import_from[2]) + matches.update(attr_matches(from_import_import[2], + from_import_from[2])) else: # `from ` completion - return (module_attr_matches(lineparts.current_from_import_from(cursor_offset, line)[2]) + - module_matches(lineparts.current_from_import_from(cursor_offset, line)[2])) - elif lineparts.current_import(cursor_offset, line): + matches = module_attr_matches(from_import_from[2]) + matches.update(module_matches(from_import_from[2])) + return matches + + current_import = lineparts.current_import(cursor_offset, line) + if current_import is not None: # `import ` completion - return (module_matches(lineparts.current_import(cursor_offset, line)[2]) + - module_attr_matches(lineparts.current_import(cursor_offset, line)[2])) + matches = module_matches(current_import[2]) + matches.update(module_attr_matches(current_import[2])) + return matches else: return None diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index b5f73512d..a4d2f8d74 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -12,10 +12,12 @@ def tearDown(self): importcompletion.modules = self.original_modules def test_simple_completion(self): - self.assertEqual(importcompletion.complete(10, 'import zza'), ['zzabc', 'zzabd']) + self.assertEqual(sorted(importcompletion.complete(10, 'import zza')), + ['zzabc', 'zzabd']) def test_package_completion(self): - self.assertEqual(importcompletion.complete(13, 'import zzabc.'), ['zzabc.e', 'zzabc.f', ]) + self.assertEqual(sorted(importcompletion.complete(13, 'import zzabc.')), + ['zzabc.e', 'zzabc.f', ]) class TestRealComplete(unittest.TestCase): @@ -32,11 +34,14 @@ def tearDownClass(cls): importcompletion.modules = set() def test_from_attribute(self): - self.assertEqual(importcompletion.complete(19, 'from sys import arg'), ['argv']) + self.assertEqual(sorted(importcompletion.complete(19, 'from sys import arg')), + ['argv']) def test_from_attr_module(self): - self.assertEqual(importcompletion.complete(9, 'from os.p'), ['os.path']) + self.assertEqual(sorted(importcompletion.complete(9, 'from os.p')), + ['os.path']) def test_from_package(self): - self.assertEqual(importcompletion.complete(17, 'from xml import d'), ['dom']) + self.assertEqual(sorted(importcompletion.complete(17, 'from xml import d')), + ['dom']) From 672bf781a51cb54842a65002f3873949add81930 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Jan 2015 21:48:16 +0100 Subject: [PATCH 0248/1650] Decode module names in attr_matches (fixes #453) Also delay set construction as long as possible Signed-off-by: Sebastian Ramacher --- bpython/importcompletion.py | 44 +++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 486075774..125a35c77 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -56,16 +56,27 @@ def catch_warnings(): fully_loaded = False +def try_decode_module(module, encoding): + """Try to decode module names.""" + if not py3 and not isinstance(module, unicode): + try: + return module.decode(encoding) + except UnicodeDecodeError: + # Not importable anyway, ignore it + return None + return module + + def module_matches(cw, prefix=''): """Modules names to replace cw with""" full = '%s.%s' % (prefix, cw) if prefix else cw - matches = set(name for name in modules - if (name.startswith(full) and - name.find('.', len(full)) == -1)) + matches = (name for name in modules + if (name.startswith(full) and + name.find('.', len(full)) == -1)) if prefix: return set(match[len(prefix)+1:] for match in matches) else: - return matches + return set(matches) def attr_matches(cw, prefix='', only_modules=False): """Attributes to replace name with""" @@ -75,17 +86,19 @@ def attr_matches(cw, prefix='', only_modules=False): return set() module = sys.modules[module_name] if only_modules: - matches = set(name for name in dir(module) - if (name.startswith(name_after_dot) and - '%s.%s' % (module_name, name)) in sys.modules) + matches = (name for name in dir(module) + if (name.startswith(name_after_dot) and + '%s.%s' % (module_name, name)) in sys.modules) else: - matches = set(name for name in dir(module) - if name.startswith(name_after_dot)) + matches = (name for name in dir(module) + if name.startswith(name_after_dot)) module_part, _, _ = cw.rpartition('.') if module_part: - matches = set('%s.%s' % (module_part, m) for m in matches) + matches = ('%s.%s' % (module_part, m) for m in matches) + + return set(filter(lambda x: x is not None, + (try_decode_module(match, 'ascii') for match in matches))) - return matches def module_attr_matches(name): """Only attributes which are modules to replace name with""" return attr_matches(name, prefix='', only_modules=True) @@ -182,12 +195,9 @@ def find_all_modules(path=None): if not p: p = os.curdir for module in find_modules(p): - if not py3 and not isinstance(module, unicode): - try: - module = module.decode(sys.getfilesystemencoding()) - except UnicodeDecodeError: - # Not importable anyway, ignore it - continue + module = try_decode_module(module, sys.getfilesystemencoding()) + if module is None: + continue modules.add(module) yield From fb46b607d0354085c96f11dc37e047ae6e817dc3 Mon Sep 17 00:00:00 2001 From: Keyan Pishdadian Date: Wed, 21 Jan 2015 18:09:39 -0500 Subject: [PATCH 0249/1650] Remove kill_line command which is duplicate of cut_to_buffer --- bpython/config.py | 2 -- bpython/curtsiesfrontend/manual_readline.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index f5d22469a..0c96b25bd 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -74,7 +74,6 @@ def loadini(struct, configfile): 'transpose_chars': 'C-t', 'clear_line': 'C-u', 'clear_screen': 'C-l', - 'kill_line': 'C-k', 'clear_word': 'C-w', 'cut_to_buffer': 'C-k', 'delete': 'C-d', @@ -172,7 +171,6 @@ def get_key_no_doublebind(command): struct.transpose_chars_key = get_key_no_doublebind('transpose_chars') struct.clear_line_key = get_key_no_doublebind('clear_line') struct.clear_screen_key = get_key_no_doublebind('clear_screen') - struct.kill_line_key = get_key_no_doublebind('kill_line') struct.exit_key = get_key_no_doublebind('exit') struct.last_output_key = get_key_no_doublebind('last_output') struct.edit_config_key = get_key_no_doublebind('edit_config') diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 3c608b14d..d2e34126a 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -250,7 +250,7 @@ def delete_line(cursor_offset, line): def uppercase_next_word(cursor_offset, line): return cursor_offset, line #TODO Not implemented -@edit_keys.on(config='kill_line_key') +@edit_keys.on(config='cut_to_buffer_key') @kills_ahead def delete_from_cursor_forward(cursor_offset, line): return cursor_offset, line[:cursor_offset], line[cursor_offset:] From 3227c426f63e84081b512c2925dc9ab1b07382be Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 21 Jan 2015 18:27:20 -0500 Subject: [PATCH 0250/1650] Fix curtsiesrepl tests --- bpython/test/test_curtsies_repl.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 762f31ab1..76c0a1424 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -2,7 +2,7 @@ import code from contextlib import contextmanager from functools import partial -from mock import Mock, patch +from mock import Mock, patch, MagicMock import os from StringIO import StringIO import sys @@ -86,12 +86,17 @@ def test_get_last_word_with_prev_line(self): self.repl.up_one_line() self.assertEqual(self.repl.current_line,'2 3') +def mock_next(obj, return_value): + if py3: + obj.__next__.return_value = return_value + else: + obj.next.return_value = return_value class TestCurtsiesReplTab(unittest.TestCase): def setUp(self): self.repl = create_repl() - self.repl.matches_iter = Mock() + self.repl.matches_iter = MagicMock() def add_matches(*args, **kwargs): self.repl.matches_iter.matches = ['aaa', 'aab', 'aac'] self.repl.complete = Mock(side_effect=add_matches, @@ -125,11 +130,11 @@ def test_tab_with_no_matches_selects_first(self): self.repl._cursor_offset = 3 self.repl.matches_iter.matches = [] self.repl.matches_iter.is_cseq.return_value = False - self.repl.matches_iter.next.return_value = None + + mock_next(self.repl.matches_iter, None) self.repl.matches_iter.cur_line.return_value = (None, None) self.repl.on_tab() self.repl.complete.assert_called_once_with(tab=True) - self.repl.matches_iter.next.assert_called_once_with() self.repl.matches_iter.cur_line.assert_called_once_with() def test_tab_with_matches_selects_next_match(self): @@ -137,10 +142,9 @@ def test_tab_with_matches_selects_next_match(self): self.repl._cursor_offset = 3 self.repl.complete() self.repl.matches_iter.is_cseq.return_value = False - self.repl.matches_iter.next.return_value = None + mock_next(self.repl.matches_iter, None) self.repl.matches_iter.cur_line.return_value = (None, None) self.repl.on_tab() - self.repl.matches_iter.next.assert_called_once_with() self.repl.matches_iter.cur_line.assert_called_once_with() def test_tab_completes_common_sequence(self): From 94e899971cdda2f8d92f325e221f886ab7b467d4 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Thu, 22 Jan 2015 12:05:01 +0100 Subject: [PATCH 0251/1650] Also show the removal URL in the duplicate paste message. --- bpython/repl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index c5937b581..881e6963b 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -314,6 +314,7 @@ def __init__(self, interp, config): # to repl.pastebin self.prev_pastebin_content = '' self.prev_pastebin_url = '' + self.prev_removal_url = '' # Necessary to fix mercurial.ui.ui expecting sys.stderr to have this # attribute self.closed = False @@ -677,8 +678,8 @@ def pastebin(self, s=None): def do_pastebin(self, s): """Actually perform the upload.""" if s == self.prev_pastebin_content: - self.interact.notify(_('Duplicate pastebin. Previous URL: %s') % - (self.prev_pastebin_url, )) + self.interact.notify(_('Duplicate pastebin. Previous URL: %s. Removal URL: %s') % + (self.prev_pastebin_url, self.prev_removal_url)) return self.prev_pastebin_url if self.config.pastebin_helper: @@ -716,6 +717,7 @@ def do_pastebin_json(self, s): removal_url = removal_url_template.safe_substitute(removal_id=removal_id) self.prev_pastebin_url = paste_url + self.prev_removal_url = removal_url self.interact.notify(_('Pastebin URL: %s - Removal URL: %s') % (paste_url, removal_url)) From 940220332f60fa90f56ff3e8f8ab47e69db4c05c Mon Sep 17 00:00:00 2001 From: Keyan Pishdadian Date: Thu, 22 Jan 2015 12:02:12 -0500 Subject: [PATCH 0252/1650] Change wording to be more clear, fixes issue #404 --- doc/sphinx/source/configuration.rst | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/doc/sphinx/source/configuration.rst b/doc/sphinx/source/configuration.rst index e45ee206f..a47759a66 100644 --- a/doc/sphinx/source/configuration.rst +++ b/doc/sphinx/source/configuration.rst @@ -2,14 +2,5 @@ Configuration ============= -You can copy the supplied sample-config to your home directory and move it to -``$XDG_CONFIG_HOME/bpython/config`` [#f1]_. bpython tries to find -``$XDG_CONFIG_HOME/bpython/config`` and use it as its configuration, if the -file does not exist bpython will use its documented defaults. - -.. :: Footnotes - -.. [#f1] ``$XDG_CONFIG_HOME`` defaults to ``~/.config`` if not set. - -.. include:: configuration-options.rst - +You can edit the config file by pressed F3. If a config file does not exist +you will asked if you would like to create a file. By default it will be saved to ``~/.config/bpython/config``. \ No newline at end of file From dbe46cb4ac76f65ee7d36056bd39f0aa6d31988f Mon Sep 17 00:00:00 2001 From: Keyan Pishdadian Date: Thu, 22 Jan 2015 12:05:30 -0500 Subject: [PATCH 0253/1650] Fix typo --- doc/sphinx/source/configuration.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/sphinx/source/configuration.rst b/doc/sphinx/source/configuration.rst index a47759a66..4934e6f3c 100644 --- a/doc/sphinx/source/configuration.rst +++ b/doc/sphinx/source/configuration.rst @@ -2,5 +2,6 @@ Configuration ============= -You can edit the config file by pressed F3. If a config file does not exist -you will asked if you would like to create a file. By default it will be saved to ``~/.config/bpython/config``. \ No newline at end of file +You can edit the config file by pressing F3 (default). If a config file does +not exist you will asked if you would like to create a file. By default it will +be saved to ``~/.config/bpython/config``. \ No newline at end of file From a679338ac2af7c3a717d115e39fc43d68f5d95e4 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 22 Jan 2015 13:34:55 -0500 Subject: [PATCH 0254/1650] fix #450 - run startupfile on undo --- bpython/curtsiesfrontend/repl.py | 1 + bpython/test/test_curtsies_repl.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 5caa4302c..6069d8e68 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1306,6 +1306,7 @@ def reevaluate(self, insert_into_history=False): self.display_buffer = [] self.highlighted_paren = None + self.process_event(bpythonevents.RunStartupFileEvent()) self.reevaluating = True sys.stdin = ReevaluateFakeStdin(self.stdin, self) for line in old_logical_lines: diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 76c0a1424..ed3f66c53 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -257,5 +257,17 @@ def test_complex(self): self.assertEqual(self.repl.predicted_indent('reduce(asdfasdf,'), 7) +class TestCurtsiesReevaluate(unittest.TestCase): + def setUp(self): + self.repl = create_repl() + + def test_variable_is_cleared(self): + self.repl._current_line = 'b = 10' + self.repl.on_enter() + self.assertIn('b', self.repl.interp.locals) + self.repl.undo() + self.assertNotIn('b', self.repl.interp.locals) + + if __name__ == '__main__': unittest.main() From 46c402c69ac44bcf2299268478a9750df56d6dfd Mon Sep 17 00:00:00 2001 From: Keyan Pishdadian Date: Thu, 22 Jan 2015 14:18:56 -0500 Subject: [PATCH 0255/1650] Reintroduce $XDG_CONFIG_HOME and insert include --- doc/sphinx/source/configuration.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/sphinx/source/configuration.rst b/doc/sphinx/source/configuration.rst index 4934e6f3c..640b29b05 100644 --- a/doc/sphinx/source/configuration.rst +++ b/doc/sphinx/source/configuration.rst @@ -3,5 +3,8 @@ Configuration ============= You can edit the config file by pressing F3 (default). If a config file does -not exist you will asked if you would like to create a file. By default it will -be saved to ``~/.config/bpython/config``. \ No newline at end of file +not exist you will asked if you would like to create a file. By default it will be saved to ``$XDG_CONFIG_HOME/.config/bpython/config``[#f1]_. + +.. :: Footnotes +.. [#f1] ``$XDG_CONFIG_HOME`` defaults to ``~/.config`` if not set. +.. include:: configuration-options.rst \ No newline at end of file From 9f173870c32a7f45681a3a5f447295aebcc05aff Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 22 Jan 2015 21:09:30 +0100 Subject: [PATCH 0256/1650] EOL 80 Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/configuration.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/sphinx/source/configuration.rst b/doc/sphinx/source/configuration.rst index 640b29b05..e7911e482 100644 --- a/doc/sphinx/source/configuration.rst +++ b/doc/sphinx/source/configuration.rst @@ -2,9 +2,10 @@ Configuration ============= -You can edit the config file by pressing F3 (default). If a config file does -not exist you will asked if you would like to create a file. By default it will be saved to ``$XDG_CONFIG_HOME/.config/bpython/config``[#f1]_. +You can edit the config file by pressing F3 (default). If a config file does not +exist you will asked if you would like to create a file. By default it will be +saved to ``$XDG_CONFIG_HOME/.config/bpython/config`` [#f1]_. .. :: Footnotes .. [#f1] ``$XDG_CONFIG_HOME`` defaults to ``~/.config`` if not set. -.. include:: configuration-options.rst \ No newline at end of file +.. include:: configuration-options.rst From cfa7bc36096f597dd8fb90dad5651cb84edc04d6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 22 Jan 2015 21:27:31 +0100 Subject: [PATCH 0257/1650] Also test keys that are not used by default Signed-off-by: Sebastian Ramacher --- bpython/test/test_config.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index cd51af799..cf03be947 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -79,3 +79,11 @@ def test_keybindings_unset(self): self.assertFalse(struct.help_key) + def test_keybindings_unused(self): + struct = self.load_temp_config(textwrap.dedent(""" + [keyboard] + help = F4 + """)) + + self.assertEqual(struct.help_key, 'F4') + From 07a817cab2c3c0a8882b1070c26400d5651bd0aa Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 22 Jan 2015 21:31:43 +0100 Subject: [PATCH 0258/1650] Fix test_keybindings_unused Signed-off-by: Sebastian Ramacher --- bpython/config.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 0c96b25bd..a8cfd0578 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -120,11 +120,15 @@ def loadini(struct, configfile): def get_key_no_doublebind(command): default_commands_to_keys = defaults['keyboard'] requested_key = config.get('keyboard', command) - default_command = default_keys_to_commands[requested_key] - if default_commands_to_keys[default_command] == \ - config.get('keyboard', default_command): - setattr(struct, '%s_key' % default_command, '') + try: + default_command = default_keys_to_commands[requested_key] + + if default_commands_to_keys[default_command] == \ + config.get('keyboard', default_command): + setattr(struct, '%s_key' % default_command, '') + except KeyError: + pass return requested_key From 677f6f85d09921a8e2d3de948dda3884f87bbf30 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 22 Jan 2015 21:52:03 +0100 Subject: [PATCH 0259/1650] Remove unnecessary delete=False Signed-off-by: Sebastian Ramacher --- bpython/test/test_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 2c876847a..1ed63d1a0 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -7,7 +7,7 @@ class TestExecArgs(unittest.TestCase): def test_exec_dunder_file(self): - with tempfile.NamedTemporaryFile(delete=False, mode="w") as f: + with tempfile.NamedTemporaryFile(mode="w") as f: f.write(dedent("""\ import sys sys.stderr.write(__file__) From b451f9512edb3fb696f4cd9756d30e83b4d3bd4d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 22 Jan 2015 15:49:23 -0500 Subject: [PATCH 0260/1650] fix unittest import --- bpython/test/test_curtsies_repl.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index ed3f66c53..8b3a5f7a8 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -8,12 +8,11 @@ import sys import tempfile -import unittest try: - from unittest import skip + import unittest2 as unittest except ImportError: - def skip(f): - return lambda self: None + import unittest + py3 = (sys.version_info[0] == 3) @@ -73,7 +72,7 @@ def test_get_last_word(self): self.repl.get_last_word() self.assertEqual(self.repl.current_line,'abcde3') - @skip # this is the behavior of bash - not currently implemented + @unittest.skip # this is the behavior of bash - not currently implemented def test_get_last_word_with_prev_line(self): self.repl.rl_history.entries=['1','2 3','4 5 6'] self.repl._set_current_line('abcde') @@ -251,7 +250,7 @@ def test_simple(self): self.assertEqual(self.repl.predicted_indent('def asdf():'), 4) self.assertEqual(self.repl.predicted_indent('def asdf(): return 7'), 0) - @skip + @unittest.skip def test_complex(self): self.assertEqual(self.repl.predicted_indent('[a,'), 1) self.assertEqual(self.repl.predicted_indent('reduce(asdfasdf,'), 7) From 0b15a0b58f1959d3ca593bb21fc3fc0e522dbc1e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 22 Jan 2015 22:29:23 +0100 Subject: [PATCH 0261/1650] Use the same unittest2 import block everywhere Signed-off-by: Sebastian Ramacher --- bpython/test/test_args.py | 5 ++++- bpython/test/test_config.py | 6 +++++- bpython/test/test_curtsies_coderunner.py | 6 +++++- bpython/test/test_curtsies_painting.py | 5 ++--- bpython/test/test_curtsies_parser.py | 5 ++++- bpython/test/test_curtsies_repl.py | 4 +--- bpython/test/test_importcompletion.py | 6 +++++- bpython/test/test_inspection.py | 5 ++++- bpython/test/test_interpreter.py | 9 ++++++--- bpython/test/test_keys.py | 6 +++++- bpython/test/test_line_properties.py | 12 ++++++++++-- bpython/test/test_repl.py | 4 +--- 12 files changed, 52 insertions(+), 21 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 1ed63d1a0..bb754d979 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -1,9 +1,12 @@ -import unittest import subprocess import sys import tempfile from textwrap import dedent +try: + import unittest2 as unittest +except ImportError: + import unittest class TestExecArgs(unittest.TestCase): def test_exec_dunder_file(self): diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index cf03be947..ec21926a0 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -1,8 +1,12 @@ import os -import unittest import tempfile import textwrap +try: + import unittest2 as unittest +except ImportError: + import unittest + from bpython import config TEST_THEME_PATH = os.path.join(os.path.dirname(__file__), "test.theme") diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index 80021503d..e4a6fd2bb 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -1,6 +1,10 @@ -import unittest import sys +try: + import unittest2 as unittest +except ImportError: + import unittest + from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput class TestCodeRunner(unittest.TestCase): diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 9a3823db4..cb35689fe 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -5,10 +5,9 @@ from contextlib import contextmanager try: - from unittest import skip + import unittest2 as unittest except ImportError: - def skip(f): - return lambda self: None + import unittest from curtsies.formatstringarray import FormatStringTest, fsarray from curtsies.fmtfuncs import * diff --git a/bpython/test/test_curtsies_parser.py b/bpython/test/test_curtsies_parser.py index 09cae32b3..8ff8b9f81 100644 --- a/bpython/test/test_curtsies_parser.py +++ b/bpython/test/test_curtsies_parser.py @@ -1,4 +1,7 @@ -import unittest +try: + import unittest2 as unittest +except ImportError: + import unittest from bpython.curtsiesfrontend import parse from curtsies.fmtfuncs import yellow, cyan, green, bold diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 8b3a5f7a8..7a66d5f0f 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -13,14 +13,12 @@ except ImportError: import unittest - -py3 = (sys.version_info[0] == 3) - from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter from bpython import autocomplete from bpython import config from bpython import args +from bpython._py3compat import py3 def setup_config(conf): config_struct = config.Struct() diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index a4d2f8d74..823aa4f78 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -1,6 +1,10 @@ from bpython import importcompletion -import unittest +try: + import unittest2 as unittest +except ImportError: + import unittest + class TestSimpleComplete(unittest.TestCase): diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index db4d83620..9b3edd1cc 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -1,4 +1,7 @@ -import unittest +try: + import unittest2 as unittest +except ImportError: + import unittest from bpython import inspection diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 60922f310..5ce17a741 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -1,4 +1,7 @@ -import unittest +try: + import unittest2 as unittest +except ImportError: + import unittest from bpython.curtsiesfrontend import interpreter from curtsies.fmtfuncs import * @@ -10,7 +13,7 @@ def test_syntaxerror(self): def append_to_a(message): a.append(message) - + i.write = append_to_a i.runsource('1.1.1.1') @@ -25,7 +28,7 @@ def test_traceback(self): def append_to_a(message): a.append(message) - + i.write = append_to_a def f(): diff --git a/bpython/test/test_keys.py b/bpython/test/test_keys.py index 003c8642b..046d0fbfb 100644 --- a/bpython/test/test_keys.py +++ b/bpython/test/test_keys.py @@ -1,4 +1,8 @@ -import unittest +try: + import unittest2 as unittest +except ImportError: + import unittest + import bpython.keys as keys class TestCLIKeys(unittest.TestCase): diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 0542b1161..51505906d 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -1,7 +1,15 @@ -import unittest +try: + import unittest2 as unittest +except ImportError: + import unittest + import re -from bpython.line import current_word, current_dict_key, current_dict, current_string, current_object, current_object_attribute, current_from_import_from, current_from_import_import, current_import, current_method_definition_name, current_single_word, current_string_literal_attr +from bpython.line import current_word, current_dict_key, current_dict, \ + current_string, current_object, current_object_attribute, \ + current_from_import_from, current_from_import_import, current_import, \ + current_method_definition_name, current_single_word, \ + current_string_literal_attr def cursor(s): diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 33b388497..263fd08a1 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -2,7 +2,6 @@ from itertools import islice import os import socket -import sys from mock import Mock, MagicMock @@ -11,8 +10,7 @@ except ImportError: import unittest -py3 = (sys.version_info[0] == 3) - +from bpython._py3compat import py3 from bpython import config, repl, cli, autocomplete From a84c2a289b765745f246dd8e781ffe9f8f4ea1e9 Mon Sep 17 00:00:00 2001 From: Mary Mokuolu Date: Thu, 22 Jan 2015 18:15:53 -0500 Subject: [PATCH 0262/1650] Add tests for dictionary key completion --- bpython/test/test_autocomplete.py | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 3ac4085d5..b18fb0943 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -145,3 +145,38 @@ def test_formatting_takes_just_last_part(self): self.assertEqual(self.completer.format('/hello/there/'), 'there/') self.assertEqual(self.completer.format('/hello/there'), 'there') +class MockNumPy(object): + """ + This is a mock numpy object that raises an error when it is trying to be converted to a boolean. + """ + def __nonzero__(self): + raise ValueError("The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()") + + +class TestDictKeyCompletion(unittest.TestCase): + + def test_set_of_keys_returned_when_matches_found(self): + com = autocomplete.DictKeyCompletion() + local={'d':{"ab":1, "cd":2},} + result=com.matches(2,"d[" , local) + self.assertSetEqual(result,set(["'ab'","'cd'"])) + + def test_empty_set_returned_when_eval_error(self): + com = autocomplete.DictKeyCompletion() + local={'e':{"ab":1, "cd":2},} + result=com.matches(2,"d[" , local) + self.assertSetEqual(result,set()) + + def test_empty_set_returned_when_not_dict_type(self): + com = autocomplete.DictKeyCompletion() + local={'l':["ab", "cd"],} + result=com.matches(2,"l[" , local) + self.assertSetEqual(result,set()) + + def test_obj_that_does_not_allow_bool_tests(self): + com = autocomplete.DictKeyCompletion() + local={'mNumPy':MockNumPy(),} + try: + com.matches(7,"mNumPy[" , local) + except ValueError: + raise AssertionError("Dict key completion raised value error.") From ac9633dc31bd5f12ed8552d00dfe17709ec6503b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 23 Jan 2015 00:36:35 +0100 Subject: [PATCH 0263/1650] Add ] back Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index dda294451..2c4f79e49 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -220,7 +220,7 @@ def matches(self, cursor_offset, line, locals_, **kwargs): except EvaluationError: return set() if obj and isinstance(obj, type({})) and obj.keys(): - return set("{!r}".format(k) for k in obj.keys() + return set("{!r}]".format(k) for k in obj.keys() if repr(k).startswith(orig)) else: return set() From 20d145035cabd2118a8177f1a18c00515ee1f55d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 22 Jan 2015 16:17:34 -0500 Subject: [PATCH 0264/1650] Revert "remove sample-config from setup.py" This reverts commit 9d8a6aaeb674fb213de596447d438851159f5294. Conflicts: setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 836e0eed7..46fa96466 100755 --- a/setup.py +++ b/setup.py @@ -229,6 +229,7 @@ def initialize_options(self): packages = packages, data_files = data_files, package_data = { + 'bpython': ['sample-config'], 'bpython.translations': mo_files, 'bpython.test': ['test.config', 'test.theme'] }, From 02c2412e3b6ef7e700b1d70f1ebec74751be9a4d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 22 Jan 2015 22:16:49 -0500 Subject: [PATCH 0265/1650] distribute sample-config and make locatable fixes #456 --- MANIFEST.in | 7 ++----- bpython/repl.py | 6 ++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 45293b64a..66034b0cd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,18 +2,15 @@ include .pycheckrc include AUTHORS include CHANGELOG include LICENSE -include data/bpython +include data/bpython.png include data/bpython.desktop include data/bpython.appdata.xml include doc/sphinx/source/conf.py include doc/sphinx/source/*.rst include doc/sphinx/source/logo.png -include sample-config include *.theme -include bpython/logo.png -include ROADMAP -include TODO include bpython/test/*.py include bpython/test/*.theme include bpython/translations/*/LC_MESSAGES/bpython.po include bpython/translations/*/LC_MESSAGES/bpython.mo +include bpython/sample-config diff --git a/bpython/repl.py b/bpython/repl.py index 881e6963b..65855d0e6 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -23,10 +23,10 @@ from __future__ import with_statement import code -import codecs import errno import inspect import os +import pkgutil import pydoc import requests import shlex @@ -952,10 +952,8 @@ def edit_config(self): if not (os.path.isfile(self.config.config_path)): if self.interact.confirm(_("Config file does not exist - create new from default? (y/N)")): try: + default_config = pkgutil.get_data('bpython', 'sample-config') bpython_dir, script_name = os.path.split(__file__) - with open(os.path.join(bpython_dir, "sample-config")) as f: - default_config = f.read() - containing_dir = os.path.dirname(os.path.abspath(self.config.config_path)) if not os.path.exists(containing_dir): os.makedirs(containing_dir) From 796ae49e410bbc7043bc3ff8e8d6d2c81c4e8cfa Mon Sep 17 00:00:00 2001 From: Mary Mokuolu Date: Fri, 23 Jan 2015 10:37:00 -0500 Subject: [PATCH 0266/1650] fixes #455 and #458 --- bpython/autocomplete.py | 4 ++-- bpython/test/test_autocomplete.py | 19 +++++++------------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index dda294451..c2c2a591a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -219,8 +219,8 @@ def matches(self, cursor_offset, line, locals_, **kwargs): obj = safe_eval(dexpr, locals_) except EvaluationError: return set() - if obj and isinstance(obj, type({})) and obj.keys(): - return set("{!r}".format(k) for k in obj.keys() + if isinstance(obj, dict) and obj.keys(): + return set("{0!r}".format(k) for k in obj.keys() if repr(k).startswith(orig)) else: return set() diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index b18fb0943..fc890e401 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -147,7 +147,7 @@ def test_formatting_takes_just_last_part(self): class MockNumPy(object): """ - This is a mock numpy object that raises an error when it is trying to be converted to a boolean. + This is a mock numpy object that raises an error when there is an atempt to convert it to a boolean. """ def __nonzero__(self): raise ValueError("The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()") @@ -158,25 +158,20 @@ class TestDictKeyCompletion(unittest.TestCase): def test_set_of_keys_returned_when_matches_found(self): com = autocomplete.DictKeyCompletion() local={'d':{"ab":1, "cd":2},} - result=com.matches(2,"d[" , local) - self.assertSetEqual(result,set(["'ab'","'cd'"])) + com.matches(2,"d[" , local) + self.assertSetEqual(com.matches(2,"d[" , local),set(["'ab'","'cd'"])) def test_empty_set_returned_when_eval_error(self): com = autocomplete.DictKeyCompletion() local={'e':{"ab":1, "cd":2},} - result=com.matches(2,"d[" , local) - self.assertSetEqual(result,set()) + self.assertSetEqual(com.matches(2,"d[" , local),set()) def test_empty_set_returned_when_not_dict_type(self): com = autocomplete.DictKeyCompletion() local={'l':["ab", "cd"],} - result=com.matches(2,"l[" , local) - self.assertSetEqual(result,set()) + self.assertSetEqual(com.matches(2,"l[" , local),set()) - def test_obj_that_does_not_allow_bool_tests(self): + def test_obj_that_does_not_allow_conversion_to_bool(self): com = autocomplete.DictKeyCompletion() local={'mNumPy':MockNumPy(),} - try: - com.matches(7,"mNumPy[" , local) - except ValueError: - raise AssertionError("Dict key completion raised value error.") + self.assertSetEqual(com.matches(7,"mNumPy[" , local), set()) From aaa099885d0b916a521776375909f6da07784ea2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 23 Jan 2015 19:54:08 +0100 Subject: [PATCH 0267/1650] Import clean up Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/_internal.py | 1 - bpython/curtsiesfrontend/coderunner.py | 1 - bpython/curtsiesfrontend/filewatch.py | 7 ++----- bpython/curtsiesfrontend/preprocess.py | 2 -- bpython/curtsiesfrontend/replpainter.py | 2 +- bpython/test/test_curtsies_painting.py | 2 +- bpython/test/test_curtsies_repl.py | 1 - bpython/test/test_interpreter.py | 2 +- bpython/test/test_manual_readline.py | 7 ++++++- bpython/test/test_preprocess.py | 1 - 10 files changed, 11 insertions(+), 15 deletions(-) diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 98c1d5c89..4fd166f07 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -20,7 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import sys import pydoc import bpython._internal diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 99314d08e..1bf377dae 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -11,7 +11,6 @@ import code import signal -import sys import greenlet import logging diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 6529f2e1b..07a426a63 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -1,4 +1,3 @@ -import time import os from collections import defaultdict @@ -30,9 +29,7 @@ def reset(self): self.observer.unschedule_all() def _add_module(self, path): - """Add a python module to track changes to - - Can""" + """Add a python module to track changes""" path = os.path.abspath(path) for suff in importcompletion.SUFFIXES: if path.endswith(suff): @@ -45,7 +42,7 @@ def _add_module(self, path): def _add_module_later(self, path): self.modules_to_add_later.append(path) - + def track_module(self, path): """ Begins tracking this if activated, or remembers to track later. diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index 93329ef34..124a82a4b 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -1,8 +1,6 @@ """Tools for preparing code to be run in the REPL (removing blank lines, etc)""" import re -from bpython.curtsiesfrontend.interpreter import code_finished_will_parse - #TODO specifically catch IndentationErrors instead of any syntax errors def indent_empty_lines(s, compiler): diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index c4812dd43..57d411947 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -172,7 +172,7 @@ def add_border(line): r = fsarray(output_lines[:min(rows-1, len(output_lines)-1)] + output_lines[-1:]) return r -def paint_last_events(rows, columns, names): +def paint_last_events(rows, columns, names, config): if not names: return fsarray([]) width = min(max(len(name) for name in names), columns-2) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index cb35689fe..57df7124e 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -10,7 +10,7 @@ import unittest from curtsies.formatstringarray import FormatStringTest, fsarray -from curtsies.fmtfuncs import * +from curtsies.fmtfuncs import cyan, bold, green, yellow, on_magenta, red from bpython.curtsiesfrontend.events import RefreshRequestEvent from bpython import config diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 7a66d5f0f..2c9b8d67c 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -1,7 +1,6 @@ # coding: utf8 import code from contextlib import contextmanager -from functools import partial from mock import Mock, patch, MagicMock import os from StringIO import StringIO diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 5ce17a741..2280aba7c 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -4,7 +4,7 @@ import unittest from bpython.curtsiesfrontend import interpreter -from curtsies.fmtfuncs import * +from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain class TestInterpreter(unittest.TestCase): def test_syntaxerror(self): diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 58b6550e8..9a7cf6cef 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -3,7 +3,12 @@ except ImportError: import unittest -from bpython.curtsiesfrontend.manual_readline import * +from bpython.curtsiesfrontend.manual_readline import \ + left_arrow, right_arrow, beginning_of_line, forward_word, back_word, \ + end_of_line, delete, last_word_pos, backspace, delete_from_cursor_back, \ + delete_from_cursor_forward, delete_rest_of_word, delete_word_to_cursor, \ + transpose_character_before_cursor, UnconfiguredEdits, \ + delete_word_from_cursor_back class TestManualReadline(unittest.TestCase): diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index dc12558ae..b5e337ad2 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -3,7 +3,6 @@ import difflib import inspect import re -import unittest try: import unittest2 as unittest From 7be3c6b783760c47e55a510d4f11d7cfbd5f2285 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 23 Jan 2015 19:56:04 +0100 Subject: [PATCH 0268/1650] Remove duplicate code Signed-off-by: Sebastian Ramacher --- bpython/test/test_curtsies_coderunner.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index e4a6fd2bb..26ee371d3 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -29,8 +29,6 @@ def test_simple(self): c.run_code() def test_exception(self): - orig_stdout = sys.stdout - orig_stderr = sys.stderr c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or self.orig_stderr.flush()) def ctrlc(): raise KeyboardInterrupt() From b89705d5b94034f21a76356a616e7825a1653cb3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 23 Jan 2015 20:09:07 +0100 Subject: [PATCH 0269/1650] Fix some pyflakes warnings Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 2 +- bpython/curtsiesfrontend/parse.py | 4 ++-- bpython/curtsiesfrontend/repl.py | 2 +- bpython/history.py | 6 +++--- bpython/urwid.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 1dc7b1ac1..8870b1f1f 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -142,7 +142,7 @@ def process_event(e): process_event(paste) process_event(None) # do a display before waiting for first event - for _ in find_iterator: + for unused in find_iterator: e = input_generator.send(0) if e is not None: process_event(e) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 5c089cc2e..e7e0bc042 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -33,8 +33,8 @@ def parse(s): while True: if not rest: break - d, rest = peel_off_string(rest) - stuff.append(d) + start, rest = peel_off_string(rest) + stuff.append(start) return (sum([fs_from_match(d) for d in stuff[1:]], fs_from_match(stuff[0])) if len(stuff) > 0 else FmtStr()) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 6069d8e68..5a1a0ed0c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -634,7 +634,7 @@ def only_whitespace_left_of_cursor(): front_white = (len(self.current_line[:self.cursor_offset]) - len(self.current_line[:self.cursor_offset].lstrip())) to_add = 4 - (front_white % self.config.tab_length) - for _ in range(to_add): + for unused in range(to_add): self.add_normal_character(' ') return diff --git a/bpython/history.py b/bpython/history.py index 1ce144d57..1b1de2926 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -189,7 +189,7 @@ def reset(self): def load(self, filename, encoding): with codecs.open(filename, 'r', encoding, 'ignore') as hfile: - with FileLock(hfile) as lock: + with FileLock(hfile): self.entries = self.load_from(hfile) @@ -202,7 +202,7 @@ def load_from(self, fd): def save(self, filename, encoding, lines=0): with codecs.open(filename, 'w', encoding, 'ignore') as hfile: - with FileLock(hfile) as lock: + with FileLock(hfile): self.save_to(hfile, self.entries, lines) @@ -220,7 +220,7 @@ def append_reload_and_write(self, s, filename, encoding): try: with codecs.open(filename, 'a+', encoding, 'ignore') as hfile: - with FileLock(hfile) as lock: + with FileLock(hfile): # read entries hfile.seek(0, os.SEEK_SET) entries = self.load_from(hfile) diff --git a/bpython/urwid.py b/bpython/urwid.py index cb82b09ae..8d066ff0c 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -893,7 +893,7 @@ def reevaluate(self): self.scr.refresh() if self.buffer: - for _ in xrange(indent): + for unused in xrange(indent): self.tab() self.evaluating = False From 22717d3167801c6fcd68b17e1f3294bba05030b3 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 23 Jan 2015 12:42:08 -0500 Subject: [PATCH 0270/1650] fix #448 without fixing the api Really ought to have a method for changing both current_line and cursor_offset --- bpython/curtsiesfrontend/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 5a1a0ed0c..7c95cec66 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1281,12 +1281,12 @@ def take_back_buffer_line(self): self.buffer.pop() if not self.buffer: - self._set_cursor_offset(0, update_completion=False) + self._cursor_offset = 0 self.current_line = '' else: line = self.buffer[-1] indent = self.predicted_indent(line) - self.current_line = indent * ' ' + self._current_line = indent * ' ' self.cursor_offset = len(self.current_line) def reevaluate(self, insert_into_history=False): From 1eaac2c746a8735d797c65dce68347843ac5e8ea Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 00:13:12 -0500 Subject: [PATCH 0271/1650] test argspec painting --- bpython/test/test_curtsies_painting.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 57df7124e..f866ee311 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -13,8 +13,9 @@ from curtsies.fmtfuncs import cyan, bold, green, yellow, on_magenta, red from bpython.curtsiesfrontend.events import RefreshRequestEvent -from bpython import config +from bpython import config, inspection from bpython.curtsiesfrontend.repl import Repl +from bpython.curtsiesfrontend import replpainter from bpython.repl import History from bpython.curtsiesfrontend.repl import INCONSISTENT_HISTORY_MSG, CONTIGUITY_BROKEN_MSG @@ -79,6 +80,20 @@ def test_completion(self): u'Welcome to bpython! Press f'] self.assert_paint_ignoring_formatting(screen, (0, 4)) + def test_argspec(self): + def foo(x, y, z=10): + "docstring!" + pass + argspec = inspection.getargspec('foo', foo) + [1] + array = replpainter.formatted_argspec(argspec, 30, setup_config()) + screen = [(bold(cyan(u'foo'))+cyan(':')+cyan(' ')+cyan('(')+cyan('x') + + yellow(',')+yellow(' ')+bold(cyan('y'))+yellow(',') + + yellow(' ')+cyan('z')+yellow('=')+bold(cyan('10'))+yellow(')'))] + print screen + print array + self.assertFSArraysEqual(fsarray(array), fsarray(screen)) + + @contextmanager def output_to_repl(repl): old_out, old_err = sys.stdout, sys.stderr From 9579c0619ee9ce1947ab1ba6496636251f48aa3b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 00:30:33 -0500 Subject: [PATCH 0272/1650] Test attribute completion --- bpython/test/test_autocomplete.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index bff5f2dfa..c81056831 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -175,3 +175,16 @@ def test_obj_that_does_not_allow_conversion_to_bool(self): com = autocomplete.DictKeyCompletion() local={'mNumPy':MockNumPy(),} self.assertSetEqual(com.matches(7,"mNumPy[" , local), set()) + +class Foo(object): + a = 10 + def __init__(self): + self.b = 20 + def method(self, x): + pass + +class TestAttrCompletion(unittest.TestCase): + + def test_att_matches_found_on_instance(self): + com = autocomplete.AttrCompletion() + self.assertSetEqual(com.matches(2, 'a.', {'a': Foo()}), set(['a.method', 'a.a', 'a.b'])) From 17b9e14ec9124047b8eb178d66fd2a8cd64cc31e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 00:30:46 -0500 Subject: [PATCH 0273/1650] format test whitespace for linter --- bpython/test/test_autocomplete.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index c81056831..aab4c5434 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -157,24 +157,23 @@ class TestDictKeyCompletion(unittest.TestCase): def test_set_of_keys_returned_when_matches_found(self): com = autocomplete.DictKeyCompletion() - local={'d':{"ab":1, "cd":2},} - com.matches(2,"d[" , local) - self.assertSetEqual(com.matches(2,"d[" , local),set(["'ab']","'cd']"])) + local = {'d': {"ab": 1, "cd": 2}} + self.assertSetEqual(com.matches(2, "d[", local), set(["'ab']", "'cd']"])) def test_empty_set_returned_when_eval_error(self): com = autocomplete.DictKeyCompletion() - local={'e':{"ab":1, "cd":2},} - self.assertSetEqual(com.matches(2,"d[" , local),set()) + local = {'e': {"ab": 1, "cd": 2}} + self.assertSetEqual(com.matches(2, "d[", local), set()) def test_empty_set_returned_when_not_dict_type(self): com = autocomplete.DictKeyCompletion() - local={'l':["ab", "cd"],} - self.assertSetEqual(com.matches(2,"l[" , local),set()) + local = {'l': ["ab", "cd"]} + self.assertSetEqual(com.matches(2, "l[", local),set()) def test_obj_that_does_not_allow_conversion_to_bool(self): com = autocomplete.DictKeyCompletion() - local={'mNumPy':MockNumPy(),} - self.assertSetEqual(com.matches(7,"mNumPy[" , local), set()) + local = {'mNumPy': MockNumPy()} + self.assertSetEqual(com.matches(7, "mNumPy[", local), set()) class Foo(object): a = 10 From 6a17d72f8e77db1bec5edc731298b90031d8d691 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 00:35:28 -0500 Subject: [PATCH 0274/1650] simplify events by removing who parameter Wasn't being used anywhere and never has been and it should have been sender anyway, or maybe whom --- bpython/curtsiesfrontend/events.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index 84ca6a307..079d2af6e 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -15,11 +15,8 @@ def __repr__(self): class RefreshRequestEvent(curtsies.events.Event): """Request to refresh REPL display ASAP""" - def __init__(self, who='?'): - self.who = who - def __repr__(self): - return "" % (self.who,) + return "" class ScheduledRefreshRequestEvent(curtsies.events.ScheduledEvent): @@ -27,13 +24,13 @@ class ScheduledRefreshRequestEvent(curtsies.events.ScheduledEvent): Used to schedule the disappearance of status bar message that only shows for a few seconds""" - def __init__(self, when, who='?'): - self.who = who + def __init__(self, when): self.when = when # time.time() + how long def __repr__(self): - return ("" % - (self.who, self.when - time.time())) + return ("" % + (self.when - time.time())) + class RunStartupFileEvent(curtsies.events.Event): """Request to run the startup file.""" From fab3984fadabe2e34e800d417419a0f32253eca0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 00:52:31 -0500 Subject: [PATCH 0275/1650] delay disappearance of pastebin URLs --- bpython/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 65855d0e6..633d916a6 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -679,7 +679,7 @@ def do_pastebin(self, s): """Actually perform the upload.""" if s == self.prev_pastebin_content: self.interact.notify(_('Duplicate pastebin. Previous URL: %s. Removal URL: %s') % - (self.prev_pastebin_url, self.prev_removal_url)) + (self.prev_pastebin_url, self.prev_removal_url), 10) return self.prev_pastebin_url if self.config.pastebin_helper: @@ -719,7 +719,7 @@ def do_pastebin_json(self, s): self.prev_pastebin_url = paste_url self.prev_removal_url = removal_url self.interact.notify(_('Pastebin URL: %s - Removal URL: %s') % - (paste_url, removal_url)) + (paste_url, removal_url), 10) return paste_url From 343fddce5d20a685a02c43882fe385b0d2dd3ad3 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 11:41:34 -0500 Subject: [PATCH 0276/1650] test painting docstring --- bpython/test/test_curtsies_painting.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index f866ee311..e4bd7fa51 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -89,10 +89,16 @@ def foo(x, y, z=10): screen = [(bold(cyan(u'foo'))+cyan(':')+cyan(' ')+cyan('(')+cyan('x') + yellow(',')+yellow(' ')+bold(cyan('y'))+yellow(',') + yellow(' ')+cyan('z')+yellow('=')+bold(cyan('10'))+yellow(')'))] - print screen - print array self.assertFSArraysEqual(fsarray(array), fsarray(screen)) + def test_formatted_docstring(self): + actual = replpainter.formatted_docstring( + 'Returns the results\n\n' 'Also has side effects', + 40, config=setup_config()) + expected = fsarray(['Returns the results', '', 'Also has side effects']) + self.assertFSArraysEqualIgnoringFormatting(actual, expected) + + @contextmanager def output_to_repl(repl): From f446b2f9e76a5b8c094de1da29b348acbe71856e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 13:35:49 -0500 Subject: [PATCH 0277/1650] add another logging level activated with -LL --- bpython/curtsies.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 8870b1f1f..e47f6c640 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -27,22 +27,23 @@ def main(args=None, locals_=None, banner=None): config, options, exec_args = bpargs.parse(args, ( 'curtsies options', None, [ - Option('--log', '-L', action='store_true', + Option('--log', '-L', action='count', help=_("log debug messages to bpython.log")), Option('--type', '-t', action='store_true', help=_("enter lines of file as though interactively typed")), ])) + if options.log is None: + options.log = 0 + logging_levels = [logging.ERROR, logging.INFO, logging.DEBUG] + level = logging_levels[min(len(logging_levels), options.log)] + logging.getLogger('curtsies').setLevel(level) + logging.getLogger('bpython').setLevel(level) if options.log: handler = logging.FileHandler(filename='bpython.log') - logging.getLogger('curtsies').setLevel(logging.WARNING) logging.getLogger('curtsies').addHandler(handler) logging.getLogger('curtsies').propagate = False - logging.getLogger('bpython').setLevel(logging.WARNING) logging.getLogger('bpython').addHandler(handler) logging.getLogger('bpython').propagate = False - else: - logging.getLogger('bpython').setLevel(logging.ERROR) - logging.getLogger('curtsies').setLevel(logging.ERROR) interp = None paste = None From bace6210a4a0e28973269dd2691fe92179af5d16 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 13:38:25 -0500 Subject: [PATCH 0278/1650] fix display of keyboard events --- bpython/curtsiesfrontend/repl.py | 6 ++++++ bpython/curtsiesfrontend/replpainter.py | 4 ++-- bpython/test/test_curtsies_painting.py | 12 +++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 7c95cec66..691b62e8b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1154,6 +1154,12 @@ def move_screen_up(current_line_start_row): else: arr[statusbar_row, :] = paint.paint_statusbar(1, width, self.status_bar.current_line, self.config) + if self.presentation_mode: + rows = arr.height + columns = arr.width + last_key_box = paint.paint_last_events(rows, columns, [events.pp_event(x) for x in self.last_events if x], self.config) + arr[arr.height-last_key_box.height:arr.height, arr.width-last_key_box.width:arr.width] = last_key_box + if self.config.color_scheme['background'] not in ('d', 'D'): for r in range(arr.height): arr[r] = fmtstr(arr[r], bg=color_for_letter(self.config.color_scheme['background'])) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 57d411947..a5fb9cb54 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -177,8 +177,8 @@ def paint_last_events(rows, columns, names, config): return fsarray([]) width = min(max(len(name) for name in names), columns-2) output_lines = [] - output_lines.append(config.left_top_corner+config.top_border*(width+2)+config.right_top_corner) - for name in names[-(rows-2):]: + output_lines.append(config.left_top_corner+config.top_border*(width)+config.right_top_corner) + for name in names[max(0, len(names)-(rows-2)):]: output_lines.append(config.left_border+name[:width].center(width)+config.right_border) output_lines.append(config.left_bottom_corner+config.bottom_border*width+config.right_bottom_corner) return fsarray(output_lines) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index e4bd7fa51..31e383298 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -93,12 +93,18 @@ def foo(x, y, z=10): def test_formatted_docstring(self): actual = replpainter.formatted_docstring( - 'Returns the results\n\n' 'Also has side effects', - 40, config=setup_config()) + 'Returns the results\n\n' 'Also has side effects', + 40, config=setup_config()) expected = fsarray(['Returns the results', '', 'Also has side effects']) self.assertFSArraysEqualIgnoringFormatting(actual, expected) - + def test_paint_lasts_events(self): + actual = replpainter.paint_last_events(4, 100, ['a', 'b', 'c'], config=setup_config()) + expected = fsarray(["┌─┐", + "│b│", + "│c│", + "└─┘"]) + self.assertFSArraysEqualIgnoringFormatting(actual, expected) @contextmanager def output_to_repl(repl): From e7d697472cd0750e7cc279d30de6e285a0fce229 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 14:49:45 -0500 Subject: [PATCH 0279/1650] display keys from top do bottom in key display --- bpython/curtsiesfrontend/replpainter.py | 4 ++-- bpython/test/test_curtsies_painting.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index a5fb9cb54..44780163b 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -175,10 +175,10 @@ def add_border(line): def paint_last_events(rows, columns, names, config): if not names: return fsarray([]) - width = min(max(len(name) for name in names), columns-2) + width = min(max([len(name) for name in names] + [0]), columns-2) output_lines = [] output_lines.append(config.left_top_corner+config.top_border*(width)+config.right_top_corner) - for name in names[max(0, len(names)-(rows-2)):]: + for name in reversed(names[max(0, len(names)-(rows-2)):]): output_lines.append(config.left_border+name[:width].center(width)+config.right_border) output_lines.append(config.left_bottom_corner+config.bottom_border*width+config.right_bottom_corner) return fsarray(output_lines) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 31e383298..93ffebd03 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -101,8 +101,8 @@ def test_formatted_docstring(self): def test_paint_lasts_events(self): actual = replpainter.paint_last_events(4, 100, ['a', 'b', 'c'], config=setup_config()) expected = fsarray(["┌─┐", - "│b│", "│c│", + "│b│", "└─┘"]) self.assertFSArraysEqualIgnoringFormatting(actual, expected) From 70ec8e98512c1b46ed71b7a4b2e8523e6dbd0c6e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 15:06:10 -0500 Subject: [PATCH 0280/1650] wait for keypress to clear URL containing messages --- bpython/cli.py | 2 +- bpython/curtsiesfrontend/interaction.py | 9 +++++---- bpython/repl.py | 2 +- bpython/urwid.py | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 264db8526..c014f3e13 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -321,7 +321,7 @@ def confirm(self, q): return reply.lower() in (_('y'), _('yes')) - def notify(self, s, n=10): + def notify(self, s, n=10, wait_for_keypress=False): return self.statusbar.message(s, n) def file_prompt(self, s): diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index faf2dfaa3..396671336 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -54,11 +54,12 @@ def pop_permanent_message(self, msg): def has_focus(self): return self.in_prompt or self.in_confirm or self.waiting_for_refresh - def message(self, msg): + def message(self, msg, schedule_refresh=True): """Sets a temporary message""" self.message_start_time = time.time() self._message = msg - self.schedule_refresh(time.time() + self.message_time) + if schedule_refresh: + self.schedule_refresh(time.time() + self.message_time) def _check_for_expired_message(self): if self._message and time.time() > self.message_start_time + self.message_time: @@ -128,10 +129,10 @@ def should_show_message(self): return bool(self.current_line) # interaction interface - should be called from other greenlets - def notify(self, msg, n=3): + def notify(self, msg, n=3, wait_for_keypress=False): self.request_greenlet = greenlet.getcurrent() self.message_time = n - self.message(msg) + self.message(msg, schedule_refresh=wait_for_keypress) self.waiting_for_refresh = True self.request_refresh() self.main_greenlet.switch(msg) diff --git a/bpython/repl.py b/bpython/repl.py index 633d916a6..1fbbb7735 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -240,7 +240,7 @@ def __init__(self, config, statusbar=None): def confirm(self, s): raise NotImplementedError - def notify(self, s, n=10): + def notify(self, s, n=10, wait_for_keypress=False): raise NotImplementedError def file_prompt(self, s): diff --git a/bpython/urwid.py b/bpython/urwid.py index 8d066ff0c..0f249f696 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -556,7 +556,7 @@ def callback_wrapper(result): self.prompt(q, callback_wrapper, single=True) - def notify(self, s, n=10): + def notify(self, s, n=10, wait_for_keypress=False): return self.statusbar.message(s, n) def prompt(self, s, callback=None, single=False): From e88bb1eb6191f8b1f82843a58ea7a04ed38ccc7d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 16:36:29 -0500 Subject: [PATCH 0281/1650] Fix magic method completion MAGIC_METHODS constant had been a single-use generator --- bpython/autocomplete.py | 2 +- bpython/test/test_autocomplete.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 43f1777ef..207d237ad 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -43,7 +43,7 @@ ALL_MODES = (SIMPLE, SUBSTRING, FUZZY) -MAGIC_METHODS = ("__%s__" % s for s in ( +MAGIC_METHODS = tuple("__%s__" % s for s in ( "init", "repr", "str", "lt", "le", "eq", "ne", "gt", "ge", "cmp", "hash", "nonzero", "unicode", "getattr", "setattr", "get", "set", "call", "len", "getitem", "setitem", "iter", "reversed", "contains", "add", "sub", "mul", diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index aab4c5434..0da557b83 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -187,3 +187,12 @@ class TestAttrCompletion(unittest.TestCase): def test_att_matches_found_on_instance(self): com = autocomplete.AttrCompletion() self.assertSetEqual(com.matches(2, 'a.', {'a': Foo()}), set(['a.method', 'a.a', 'a.b'])) + +class TestMagicMethodCompletion(unittest.TestCase): + + def test_magic_methods_complete_after_double_underscores(self): + com = autocomplete.MagicMethodCompletion() + block = "class Something(object)\n def __" + self.assertSetEqual(com.matches(10, ' def __', block), set(autocomplete.MAGIC_METHODS)) + + From 630b91b33515f7fb4cddeaa22aec10c004d7ef0d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 23 Jan 2015 23:21:18 +0100 Subject: [PATCH 0282/1650] Update translations Signed-off-by: Sebastian Ramacher --- bpython/translations/bpython.pot | 51 +++++----- .../translations/de/LC_MESSAGES/bpython.po | 86 +++++++--------- .../translations/es_ES/LC_MESSAGES/bpython.po | 99 +++++++------------ .../translations/it_IT/LC_MESSAGES/bpython.po | 90 ++++++----------- .../translations/nl_NL/LC_MESSAGES/bpython.po | 93 ++++++----------- 5 files changed, 162 insertions(+), 257 deletions(-) diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 46e615d9d..118584890 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.13-381\n" +"Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-01-17 18:57+0100\n" +"POT-Creation-Date: 2015-01-23 23:19+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -81,91 +81,91 @@ msgstr "" msgid "Error occurded while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:471 +#: bpython/repl.py:477 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:476 +#: bpython/repl.py:482 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:481 +#: bpython/repl.py:487 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:483 +#: bpython/repl.py:489 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:649 +#: bpython/repl.py:655 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:656 +#: bpython/repl.py:662 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:664 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:673 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:668 +#: bpython/repl.py:674 msgid "Pastebin aborted" msgstr "" -#: bpython/repl.py:675 +#: bpython/repl.py:681 #, python-format -msgid "Duplicate pastebin. Previous URL: %s" +msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:694 bpython/repl.py:721 +#: bpython/repl.py:700 bpython/repl.py:728 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:699 +#: bpython/repl.py:705 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:714 +#: bpython/repl.py:721 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:733 +#: bpython/repl.py:740 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:736 +#: bpython/repl.py:743 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:743 +#: bpython/repl.py:750 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:747 +#: bpython/repl.py:754 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:754 +#: bpython/repl.py:761 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:760 +#: bpython/repl.py:767 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:946 +#: bpython/repl.py:953 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" @@ -205,3 +205,8 @@ msgstr "" msgid "Press <%s> for help." msgstr "" +#: bpython/curtsiesfrontend/repl.py:470 +#, python-format +msgid "Executing PYTHONSTARTUP failed: %s" +msgstr "" + diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 01b8f57ab..99ccc970a 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,23 +7,21 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-01-17 18:57+0100\n" -"PO-Revision-Date: 2015-01-17 22:13+0100\n" +"POT-Creation-Date: 2015-01-23 23:19+0100\n" +"PO-Revision-Date: 2015-01-23 23:19+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: de \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -"Language: de\n" -"X-Generator: Poedit 1.6.10\n" #: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back to " -"the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back " +"to the regular Python interpreter." msgstr "" #: bpython/args.py:67 @@ -52,7 +50,7 @@ msgstr "ja" #: bpython/cli.py:1696 msgid "Rewind" -msgstr "" +msgstr "Rückgängig" #: bpython/cli.py:1697 msgid "Save" @@ -83,97 +81,95 @@ msgstr "" msgid "Error occurded while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" -#: bpython/repl.py:471 +#: bpython/repl.py:477 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:476 +#: bpython/repl.py:482 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" -#: bpython/repl.py:481 +#: bpython/repl.py:487 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/repl.py:483 +#: bpython/repl.py:489 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" -#: bpython/repl.py:649 +#: bpython/repl.py:655 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/repl.py:656 +#: bpython/repl.py:662 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/repl.py:658 +#: bpython/repl.py:664 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:667 +#: bpython/repl.py:673 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:668 +#: bpython/repl.py:674 msgid "Pastebin aborted" msgstr "" -#: bpython/repl.py:675 +#: bpython/repl.py:681 #, python-format -msgid "Duplicate pastebin. Previous URL: %s" +msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:694 bpython/repl.py:721 +#: bpython/repl.py:700 bpython/repl.py:728 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." -#: bpython/repl.py:699 +#: bpython/repl.py:705 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:714 +#: bpython/repl.py:721 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:733 +#: bpython/repl.py:740 msgid "Upload failed: Helper program not found." -msgstr "" -"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht gefunden werden." +msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht gefunden werden." -#: bpython/repl.py:736 +#: bpython/repl.py:743 msgid "Upload failed: Helper program could not be run." msgstr "" -"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht ausgeführt werden." +"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht ausgeführt " +"werden." -#: bpython/repl.py:743 +#: bpython/repl.py:750 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm beendete mit Status %s." -#: bpython/repl.py:747 +#: bpython/repl.py:754 msgid "Upload failed: No output from helper program." -msgstr "" -"Hochladen ist fehlgeschlagen: Keine Ausgabe von Hilfsprogramm vorhanden." +msgstr "Hochladen ist fehlgeschlagen: Keine Ausgabe von Hilfsprogramm vorhanden." -#: bpython/repl.py:754 -msgid "" -"Upload failed: Failed to recognize the helper program's output as an URL." +#: bpython/repl.py:761 +msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" "Hochladen ist fehlgeschlagen: Konte Ausgabe von Hilfsprogramm nicht " "verarbeiten." -#: bpython/repl.py:760 +#: bpython/repl.py:767 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:946 +#: bpython/repl.py:953 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" @@ -213,17 +209,7 @@ msgstr "Willkommen by bpython!" msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#~ msgid "An error occurred." -#~ msgstr "Ein Fehler ist aufgetreten." - -#~ msgid "Exception details" -#~ msgstr "Ausnahmedetails" - -#~ msgid "Statusbar" -#~ msgstr "Statusleiste" - -#~ msgid "Python files" -#~ msgstr "Python Dateien" - -#~ msgid "All files" -#~ msgstr "Alle Dateien" +#: bpython/curtsiesfrontend/repl.py:470 +#, python-format +msgid "Executing PYTHONSTARTUP failed: %s" +msgstr "" diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 06a82322e..9df0ebad4 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,9 +7,9 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-01-17 18:57+0100\n" -"PO-Revision-Date: 2013-10-11 14:46+0200\n" -"Last-Translator: Claudia Medde\n" +"POT-Creation-Date: 2015-01-23 23:19+0100\n" +"PO-Revision-Date: 2015-01-23 23:20+0100\n" +"Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" @@ -65,9 +65,8 @@ msgid "Pager" msgstr "" #: bpython/cli.py:1700 -#, fuzzy msgid "Show Source" -msgstr "Imposible mostrar el código fuente" +msgstr "" #: bpython/curtsies.py:31 msgid "log debug messages to bpython.log" @@ -82,91 +81,91 @@ msgstr "" msgid "Error occurded while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:471 +#: bpython/repl.py:477 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:476 -#, fuzzy, python-format +#: bpython/repl.py:482 +#, python-format msgid "Cannot get source: %s" -msgstr "Imposible mostrar el código fuente" +msgstr "" -#: bpython/repl.py:481 -#, fuzzy, python-format +#: bpython/repl.py:487 +#, python-format msgid "Cannot access source of %r" -msgstr "Imposible mostrar el código fuente" +msgstr "" -#: bpython/repl.py:483 +#: bpython/repl.py:489 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:649 +#: bpython/repl.py:655 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:656 +#: bpython/repl.py:662 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:664 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:673 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:668 +#: bpython/repl.py:674 msgid "Pastebin aborted" msgstr "" -#: bpython/repl.py:675 +#: bpython/repl.py:681 #, python-format -msgid "Duplicate pastebin. Previous URL: %s" +msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:694 bpython/repl.py:721 +#: bpython/repl.py:700 bpython/repl.py:728 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:699 +#: bpython/repl.py:705 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:714 +#: bpython/repl.py:721 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:733 +#: bpython/repl.py:740 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:736 +#: bpython/repl.py:743 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:743 +#: bpython/repl.py:750 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:747 +#: bpython/repl.py:754 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:754 +#: bpython/repl.py:761 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:760 +#: bpython/repl.py:767 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:946 +#: bpython/repl.py:953 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" @@ -200,45 +199,15 @@ msgid "Port to run an eval server on (forces Twisted)." msgstr "" #: bpython/curtsiesfrontend/repl.py:258 -#, fuzzy msgid "Welcome to bpython!" -msgstr "Embed bpython" +msgstr "" #: bpython/curtsiesfrontend/repl.py:258 #, python-format msgid "Press <%s> for help." msgstr "" -#~ msgid "An error occurred." -#~ msgstr "" - -#~ msgid "Exception details" -#~ msgstr "" - -#~ msgid "Statusbar" -#~ msgstr "Statusbar" - -#~ msgid "tooltip" -#~ msgstr "tooltip" - -#~ msgid "File to save to" -#~ msgstr "" - -#~ msgid "Python files" -#~ msgstr "Files Python" - -#~ msgid "All files" -#~ msgstr "Todos los files" - -#~ msgid "gtk-specific options" -#~ msgstr "" - -#~ msgid "Options specific to bpython's Gtk+ front end" -#~ msgstr "" - -#~ msgid "Pastebin selection" -#~ msgstr "Pastebin la selección" - -#~ msgid "Pastebin error for URL '%s': %s" -#~ msgstr "" - +#: bpython/curtsiesfrontend/repl.py:470 +#, python-format +msgid "Executing PYTHONSTARTUP failed: %s" +msgstr "" diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index 10bcee5fa..0fa791916 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,23 +7,21 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-01-17 18:57+0100\n" -"PO-Revision-Date: 2015-01-17 22:15+0100\n" +"POT-Creation-Date: 2015-01-23 23:19+0100\n" +"PO-Revision-Date: 2015-01-23 23:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: Michele Orrù\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -"Language: it_IT\n" -"X-Generator: Poedit 1.6.10\n" #: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back to " -"the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back " +"to the regular Python interpreter." msgstr "" #: bpython/args.py:67 @@ -83,100 +81,98 @@ msgstr "" msgid "Error occurded while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:471 +#: bpython/repl.py:477 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:476 +#: bpython/repl.py:482 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:481 +#: bpython/repl.py:487 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:483 +#: bpython/repl.py:489 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:649 +#: bpython/repl.py:655 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:656 +#: bpython/repl.py:662 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:664 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:673 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:668 +#: bpython/repl.py:674 msgid "Pastebin aborted" msgstr "" -#: bpython/repl.py:675 +#: bpython/repl.py:681 #, python-format -msgid "Duplicate pastebin. Previous URL: %s" +msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:694 bpython/repl.py:721 +#: bpython/repl.py:700 bpython/repl.py:728 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:699 +#: bpython/repl.py:705 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:714 +#: bpython/repl.py:721 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:733 +#: bpython/repl.py:740 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:736 +#: bpython/repl.py:743 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:743 +#: bpython/repl.py:750 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:747 +#: bpython/repl.py:754 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:754 -msgid "" -"Upload failed: Failed to recognize the helper program's output as an URL." +#: bpython/repl.py:761 +msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:760 +#: bpython/repl.py:767 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:946 +#: bpython/repl.py:953 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" #: bpython/urwid.py:617 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " -msgstr "" -" <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" +msgstr " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" #: bpython/urwid.py:1126 msgid "Run twisted reactor." @@ -209,29 +205,7 @@ msgstr "" msgid "Press <%s> for help." msgstr "" -#~ msgid "An error occurred." -#~ msgstr "È stato riscontrato un errore" - -#~ msgid "Exception details" -#~ msgstr "Dettagli sull'eccezione" - -#~ msgid "Statusbar" -#~ msgstr "Barra di stato" - -#~ msgid "tooltip" -#~ msgstr "tooltip" - -#~ msgid "File to save to" -#~ msgstr "File nel quale salvare" - -#~ msgid "Python files" -#~ msgstr "Files python" - -#~ msgid "All files" -#~ msgstr "Tutti i files" - -#~ msgid "gtk-specific options" -#~ msgstr "Opzioni specifiche di gtk" - -#~ msgid "Options specific to bpython's Gtk+ front end" -#~ msgstr "Opzioni specifiche riguardo il frontend gtk+ di bpython" +#: bpython/curtsiesfrontend/repl.py:470 +#, python-format +msgid "Executing PYTHONSTARTUP failed: %s" +msgstr "" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index b1b3f7a5f..da7519cde 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,23 +7,21 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-01-17 18:57+0100\n" -"PO-Revision-Date: 2015-01-17 22:14+0100\n" +"POT-Creation-Date: 2015-01-23 23:19+0100\n" +"PO-Revision-Date: 2015-01-23 23:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -"Language: nl_NL\n" -"X-Generator: Poedit 1.6.10\n" #: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back to " -"the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back " +"to the regular Python interpreter." msgstr "" #: bpython/args.py:67 @@ -83,100 +81,98 @@ msgstr "" msgid "Error occurded while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:471 +#: bpython/repl.py:477 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:476 +#: bpython/repl.py:482 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:481 +#: bpython/repl.py:487 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:483 +#: bpython/repl.py:489 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:649 +#: bpython/repl.py:655 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:656 +#: bpython/repl.py:662 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:664 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:673 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:668 +#: bpython/repl.py:674 msgid "Pastebin aborted" msgstr "" -#: bpython/repl.py:675 +#: bpython/repl.py:681 #, python-format -msgid "Duplicate pastebin. Previous URL: %s" +msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:694 bpython/repl.py:721 +#: bpython/repl.py:700 bpython/repl.py:728 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:699 +#: bpython/repl.py:705 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:714 +#: bpython/repl.py:721 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:733 +#: bpython/repl.py:740 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:736 +#: bpython/repl.py:743 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:743 +#: bpython/repl.py:750 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:747 +#: bpython/repl.py:754 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:754 -msgid "" -"Upload failed: Failed to recognize the helper program's output as an URL." +#: bpython/repl.py:761 +msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:760 +#: bpython/repl.py:767 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:946 +#: bpython/repl.py:953 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" #: bpython/urwid.py:617 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " -msgstr "" -" <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" +msgstr " <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" #: bpython/urwid.py:1126 msgid "Run twisted reactor." @@ -209,32 +205,7 @@ msgstr "" msgid "Press <%s> for help." msgstr "" -#~ msgid "An error occurred." -#~ msgstr "Er is een fout opgetreden" - -#~ msgid "Exception details" -#~ msgstr "Fout details" - -#~ msgid "Statusbar" -#~ msgstr "Statusbalk" - -#~ msgid "tooltip" -#~ msgstr "tooltip" - -#~ msgid "File to save to" -#~ msgstr "Bestandsnaaam" - -#~ msgid "Python files" -#~ msgstr "Python bestanden" - -#~ msgid "All files" -#~ msgstr "Alle bestanden" - -#~ msgid "gtk-specific options" -#~ msgstr "gtk-specifieke opties" - -#~ msgid "Options specific to bpython's Gtk+ front end" -#~ msgstr "Opties specifiek voor bpythons Gtk+ front end" - -#~ msgid "Pastebin selection" -#~ msgstr "Pastebin de selectie" +#: bpython/curtsiesfrontend/repl.py:470 +#, python-format +msgid "Executing PYTHONSTARTUP failed: %s" +msgstr "" From e3311cd949254c87dd687dfdfefedd935af316b0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 24 Jan 2015 23:44:52 +0100 Subject: [PATCH 0283/1650] Enable Travis CI webhook --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index a2ad7aa6a..2d1059c05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: python sudo: false +notifications: + webhooks: http://ramacher.at:2810/7e210HhiKpMbF7L9/travisci python: - "2.6" From 57b94b08f65984520767c37b550b0469b8ac06cc Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 14 Jan 2015 06:11:07 +0100 Subject: [PATCH 0284/1650] Jedi completion only for multiline blocks --- bpython/autocomplete.py | 45 ++++++++++++++++++++++++++-- bpython/curtsiesfrontend/repl.py | 6 ++-- bpython/repl.py | 3 +- bpython/test/test_autocomplete.py | 6 ++-- bpython/test/test_line_properties.py | 4 +++ 5 files changed, 54 insertions(+), 10 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 207d237ad..80e9ddd20 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -31,6 +31,8 @@ from glob import glob +import jedi + from bpython import inspection from bpython import importcompletion from bpython import line as lineparts @@ -114,7 +116,7 @@ def locate(self, current_offset, line): def format(self, word): return self._completers[0].format(word) - def matches(self, cursor_offset, line, locals_, argspec, current_block, complete_magic_methods): + def matches(self, cursor_offset, line, locals_, argspec, current_block, complete_magic_methods, history): all_matches = set() for completer in self._completers: # these have to be explicitely listed to deal with the different @@ -124,7 +126,8 @@ def matches(self, cursor_offset, line, locals_, argspec, current_block, complete locals_=locals_, argspec=argspec, current_block=current_block, - complete_magic_methods=complete_magic_methods) + complete_magic_methods=complete_magic_methods, + history=history) if matches is not None: all_matches.update(matches) @@ -308,6 +311,43 @@ def matches(self, cursor_offset, line, **kwargs): def locate(self, current_offset, line): return lineparts.current_string_literal_attr(current_offset, line) +class JediCompletion(BaseCompletionType): + @classmethod + def matches(self, cursor_offset, line, history, **kwargs): + if not lineparts.current_word(cursor_offset, line): + return None + history = '\n'.join(history) + '\n' + line + script = jedi.Script(history, len(history.splitlines()), cursor_offset, 'fake.py') + completions = script.completions() + if completions: + self._original = completions[0] + else: + self._original = None + + matches = [c.name for c in completions] + if all(m.startswith('_') for m in matches): + return matches + elif any(not m.startswith(matches[0][0]) for m in matches): + return matches + else: + return [m for m in matches if not m.startswith('_')] + + def locate(self, cursor_offset, line): + start = cursor_offset - (len(self._original.name) - len(self._original.complete)) + end = cursor_offset + return start, end, line[start:end] + + +class MultilineJediCompletion(JediCompletion): + @classmethod + def matches(cls, cursor_offset, line, current_block, history, **kwargs): + if '\n' in current_block: + assert cursor_offset <= len(line), "%r %r" % (cursor_offset, line) + results = JediCompletion.matches(cursor_offset, line, history) + return results + else: + return None + def get_completer(completers, cursor_offset, line, **kwargs): """Returns a list of matches and an applicable completer @@ -338,6 +378,7 @@ def get_completer(completers, cursor_offset, line, **kwargs): ImportCompletion(), FilenameCompletion(), MagicMethodCompletion(), + MultilineJediCompletion(), GlobalCompletion(), CumulativeCompleter((AttrCompletion(), ParameterNameCompletion())) ) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 691b62e8b..f5d22f1bd 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -607,9 +607,7 @@ def readline_kill(self, e): self.cut_buffer = cut def on_enter(self, insert_into_history=True): - self.cursor_offset = -1 # so the cursor isn't touching a paren - self.unhighlight_paren() # in unhighlight_paren - self.highlighted_paren = None + self._set_cursor_offset(-1, update_completion=False) # so the cursor isn't touching a paren self.history.append(self.current_line) self.push(self.current_line, insert_into_history=insert_into_history) @@ -866,7 +864,7 @@ def run_code_and_maybe_finish(self, for_code=None): self.display_lines.extend(paint.display_linize(self.current_stdouterr_line, self.width)) self.current_stdouterr_line = '' - self.current_line = ' '*indent + self._set_current_line(' '*indent, update_completion=True) self.cursor_offset = len(self.current_line) def keyboard_interrupt(self): diff --git a/bpython/repl.py b/bpython/repl.py index 1fbbb7735..1fd1aef86 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -534,7 +534,8 @@ def complete(self, tab=False): locals_=self.interp.locals, argspec=self.argspec, current_block='\n'.join(self.buffer + [self.current_line]), - complete_magic_methods=self.config.complete_magic_methods) + complete_magic_methods=self.config.complete_magic_methods, + history=self.history) #TODO implement completer.shown_before_tab == False (filenames shouldn't fill screen) if len(matches) == 0: diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 0da557b83..cbc90e6ea 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -83,18 +83,18 @@ def test_no_completers_fails(self): def test_one_empty_completer_returns_empty(self): a = self.completer([]) cumulative = autocomplete.CumulativeCompleter([a]) - self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1), []) + self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1, 1), []) def test_one_none_completer_returns_empty(self): a = self.completer(None) cumulative = autocomplete.CumulativeCompleter([a]) - self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1), []) + self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1, 1), []) def test_two_completers_get_both(self): a = self.completer(['a']) b = self.completer(['b']) cumulative = autocomplete.CumulativeCompleter([a, b]) - self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1), (['a', 'b'])) + self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1, 1), (['a', 'b'])) class TestFilenameCompletion(unittest.TestCase): diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 51505906d..93bf6aa2f 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -124,6 +124,10 @@ def test_dots(self): self.assertAccess('stuff[]') self.assertAccess('stuff[asdf[]') + def test_open_paren(self): + self.assertAccess('') + # documenting current behavior - TODO is this intended? + class TestCurrentDictKey(LineTestCase): def setUp(self): self.func = current_dict_key From 6a38aab7ab5efcae0f5290aac83cb59501007261 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 15:52:32 -0500 Subject: [PATCH 0285/1650] make jedi optional dependency --- .travis.install.sh | 2 + bpython/autocomplete.py | 82 +++++++++++++++++-------------- bpython/test/test_autocomplete.py | 4 -- setup.py | 3 +- 4 files changed, 49 insertions(+), 42 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index 54fd66afc..219886e34 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -9,6 +9,8 @@ if [[ $RUN == nosetests ]]; then pip install pygments requests 'curtsies >=0.1.17,<0.2.0' greenlet # filewatch specific dependencies pip install watchdog + # jedi specific dependencies + pip install jedi # translation specific dependencies pip install babel # Python 2.6 specific dependencies diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 80e9ddd20..e58a4d232 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -31,8 +31,6 @@ from glob import glob -import jedi - from bpython import inspection from bpython import importcompletion from bpython import line as lineparts @@ -311,42 +309,52 @@ def matches(self, cursor_offset, line, **kwargs): def locate(self, current_offset, line): return lineparts.current_string_literal_attr(current_offset, line) -class JediCompletion(BaseCompletionType): - @classmethod - def matches(self, cursor_offset, line, history, **kwargs): - if not lineparts.current_word(cursor_offset, line): - return None - history = '\n'.join(history) + '\n' + line - script = jedi.Script(history, len(history.splitlines()), cursor_offset, 'fake.py') - completions = script.completions() - if completions: - self._original = completions[0] - else: - self._original = None - - matches = [c.name for c in completions] - if all(m.startswith('_') for m in matches): - return matches - elif any(not m.startswith(matches[0][0]) for m in matches): - return matches - else: - return [m for m in matches if not m.startswith('_')] - - def locate(self, cursor_offset, line): - start = cursor_offset - (len(self._original.name) - len(self._original.complete)) - end = cursor_offset - return start, end, line[start:end] - - -class MultilineJediCompletion(JediCompletion): - @classmethod - def matches(cls, cursor_offset, line, current_block, history, **kwargs): - if '\n' in current_block: - assert cursor_offset <= len(line), "%r %r" % (cursor_offset, line) - results = JediCompletion.matches(cursor_offset, line, history) - return results - else: +try: + import jedi +except ImportError: + class MultilineJediCompletion(BaseCompletionType): + @classmethod + def matches(cls, cursor_offset, line, **kwargs): return None +else: + class JediCompletion(BaseCompletionType): + @classmethod + def matches(self, cursor_offset, line, history, **kwargs): + if not lineparts.current_word(cursor_offset, line): + return None + history = '\n'.join(history) + '\n' + line + script = jedi.Script(history, len(history.splitlines()), cursor_offset, 'fake.py') + completions = script.completions() + if completions: + self._orig_start = cursor_offset - (len(completions[0].name) - len(completions[0].complete)) + else: + self._orig_start = None + return None + + first_letter = line[self._orig_start:self._orig_start+1] + + matches = [c.name for c in completions] + if any(not m.lower().startswith(matches[0][0].lower()) for m in matches): + return None # Too general - giving completions starting with multiple letters + else: + # case-sensitive matches only + return [m for m in matches if m.startswith(first_letter)] + + def locate(self, cursor_offset, line): + start = self._orig_start + end = cursor_offset + return start, end, line[start:end] + + + class MultilineJediCompletion(JediCompletion): + @classmethod + def matches(cls, cursor_offset, line, current_block, history, **kwargs): + if '\n' in current_block: + assert cursor_offset <= len(line), "%r %r" % (cursor_offset, line) + results = JediCompletion.matches(cursor_offset, line, history) + return results + else: + return None def get_completer(completers, cursor_offset, line, **kwargs): diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index cbc90e6ea..73e15d6ba 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -6,10 +6,6 @@ except ImportError: import unittest -#TODO: Parts of autocompletion to test: -# Test that the right matches come back from find_matches (test that priority is correct) -# Test the various complete methods (import, filename) to see if right matches -# Test that MatchesIterator.substitute correctly subs given a match and a completer class TestSafeEval(unittest.TestCase): def test_catches_syntax_error(self): diff --git a/setup.py b/setup.py index 46fa96466..55297abc7 100755 --- a/setup.py +++ b/setup.py @@ -174,7 +174,8 @@ def initialize_options(self): extras_require = { 'urwid' : ['urwid'], - 'watch' : ['watchdog'] + 'watch' : ['watchdog'], + 'jedi' : ['jedi'], } packages = [ From fed23ca5d6bc54c51333814cc3f176605e4f04c0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 24 Jan 2015 18:19:25 -0500 Subject: [PATCH 0286/1650] add jedi completion tests --- bpython/autocomplete.py | 11 +++----- bpython/test/test_autocomplete.py | 42 ++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index e58a4d232..c34a43139 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -313,12 +313,10 @@ def locate(self, current_offset, line): import jedi except ImportError: class MultilineJediCompletion(BaseCompletionType): - @classmethod - def matches(cls, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs): return None else: class JediCompletion(BaseCompletionType): - @classmethod def matches(self, cursor_offset, line, history, **kwargs): if not lineparts.current_word(cursor_offset, line): return None @@ -338,7 +336,7 @@ def matches(self, cursor_offset, line, history, **kwargs): return None # Too general - giving completions starting with multiple letters else: # case-sensitive matches only - return [m for m in matches if m.startswith(first_letter)] + return set([m for m in matches if m.startswith(first_letter)]) def locate(self, cursor_offset, line): start = self._orig_start @@ -347,11 +345,10 @@ def locate(self, cursor_offset, line): class MultilineJediCompletion(JediCompletion): - @classmethod - def matches(cls, cursor_offset, line, current_block, history, **kwargs): + def matches(self, cursor_offset, line, current_block, history, **kwargs): if '\n' in current_block: assert cursor_offset <= len(line), "%r %r" % (cursor_offset, line) - results = JediCompletion.matches(cursor_offset, line, history) + results = JediCompletion.matches(self, cursor_offset, line, history) return results else: return None diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 73e15d6ba..caa16521d 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -1,3 +1,4 @@ +from collections import namedtuple from bpython import autocomplete import mock @@ -6,6 +7,12 @@ except ImportError: import unittest +try: + import jedi + has_jedi = True +except ImportError: + has_jedi = False + class TestSafeEval(unittest.TestCase): def test_catches_syntax_error(self): @@ -191,4 +198,37 @@ def test_magic_methods_complete_after_double_underscores(self): block = "class Something(object)\n def __" self.assertSetEqual(com.matches(10, ' def __', block), set(autocomplete.MAGIC_METHODS)) - +Comp = namedtuple('Completion', ['name', 'complete']) + +class TestMultilineJediCompletion(unittest.TestCase): + + @unittest.skipIf(not has_jedi, "jedi not available") + def test_returns_none_with_single_line(self): + com = autocomplete.MultilineJediCompletion() + self.assertEqual(com.matches(2, 'Va', 'Va', []), None) + + @unittest.skipIf(not has_jedi, "jedi not available") + def test_returns_non_with_blank_second_line(self): + com = autocomplete.MultilineJediCompletion() + self.assertEqual(com.matches(0, '', 'class Foo():\n', ['class Foo():']), None) + + def matches_from_completions(self, cursor, line, block, history, completions): + with mock.patch('bpython.autocomplete.jedi.Script') as Script: + script = Script.return_value + script.completions.return_value = completions + com = autocomplete.MultilineJediCompletion() + return com.matches(cursor, line, block, history) + + @unittest.skipIf(not has_jedi, "jedi not available") + def test_completions_starting_with_different_letters(self): + matches = self.matches_from_completions( + 2, ' a', 'class Foo:\n a', ['adsf'], + [Comp('Abc', 'bc'), Comp('Cbc', 'bc')]) + self.assertEqual(matches, None) + + @unittest.skipIf(not has_jedi, "jedi not available") + def test_completions_starting_with_different_cases(self): + matches = self.matches_from_completions( + 2, ' a', 'class Foo:\n a', ['adsf'], + [Comp('Abc', 'bc'), Comp('ade', 'de')]) + self.assertSetEqual(matches, set(['ade'])) From b4086b177adf5663b776467038957bd111b425ef Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Jan 2015 10:17:31 +0100 Subject: [PATCH 0287/1650] Encrypt webhook URL Signed-off-by: Sebastian Ramacher --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2d1059c05..73b56480b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: python sudo: false notifications: - webhooks: http://ramacher.at:2810/7e210HhiKpMbF7L9/travisci + webhooks: + - secure: "QXcEHVnOi5mZpONkHSu1tydj8EK3G7xJ7Wv/WYhJ5soNUpEJgi6YwR1WcxSjo7qyi8hTL+4jc+ID0TpKDeS1lpXF41kG9xf5kdxw5OL0EnMkrP9okUN0Ip8taEhd8w+6+dGmfZrx2nXOg1kBU7W5cE90XYqEtNDVXXgNeilT+ik=" python: - "2.6" From babd41f1ffbc01195d742866031076df2b03af03 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 25 Jan 2015 16:14:23 -0500 Subject: [PATCH 0288/1650] Predict how long an undo will take and prompt for how many lines --- bpython/cli.py | 4 +- bpython/curtsies.py | 2 + bpython/curtsiesfrontend/events.py | 6 ++ bpython/curtsiesfrontend/interaction.py | 10 ++-- bpython/curtsiesfrontend/interpreter.py | 19 +++++-- bpython/curtsiesfrontend/repl.py | 14 ++++- bpython/repl.py | 76 ++++++++++++++++++++++--- 7 files changed, 112 insertions(+), 19 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index c014f3e13..0a65a3b5e 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -878,7 +878,9 @@ def p_key(self, key): return '' elif key in key_dispatch[config.undo_key]: # C-r - self.undo() + n = self.prompt_undo() + if n > 0: + self.undo(n=n) return '' elif key in key_dispatch[config.search_key]: diff --git a/bpython/curtsies.py b/bpython/curtsies.py index e47f6c640..d9189b843 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -82,6 +82,7 @@ def mainloop(config, locals_, banner, interp=None, paste=None, interactive=True) schedule_refresh = input_generator.scheduled_event_trigger(bpythonevents.ScheduledRefreshRequestEvent) request_reload = input_generator.threadsafe_event_trigger(bpythonevents.ReloadEvent) interrupting_refresh = input_generator.threadsafe_event_trigger(lambda: None) + request_undo = input_generator.event_trigger(bpythonevents.UndoEvent) def on_suspend(): window.__exit__(None, None, None) @@ -98,6 +99,7 @@ def after_suspend(): request_refresh=request_refresh, schedule_refresh=schedule_refresh, request_reload=request_reload, + request_undo=request_undo, get_term_hw=window.get_term_hw, get_cursor_vertical_diff=window.get_cursor_vertical_diff, banner=banner, diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index 079d2af6e..982c96655 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -34,3 +34,9 @@ def __repr__(self): class RunStartupFileEvent(curtsies.events.Event): """Request to run the startup file.""" + + +class UndoEvent(curtsies.events.Event): + """Request to undo.""" + def __init__(self, n=1): + self.n = n diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 396671336..ce83ba5bf 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -74,11 +74,14 @@ def process_event(self, e): elif isinstance(e, events.PasteEvent): for ee in e.events: self.add_normal_character(ee if len(ee) == 1 else ee[-1]) #strip control seq + elif e in [''] or isinstance(e, events.SigIntEvent): + self.request_greenlet.switch(False) + self.escape() elif e in edit_keys: self.cursor_offset_in_line, self._current_line = edit_keys[e](self.cursor_offset_in_line, self._current_line) - elif e == "": + elif e == "": #TODO can this be removed? raise KeyboardInterrupt() - elif e == "": + elif e == "": #TODO this isn't a very intuitive behavior raise SystemExit() elif self.in_prompt and e in ("\n", "\r", "", "Ctrl-m>"): line = self._current_line @@ -90,9 +93,6 @@ def process_event(self, e): else: self.request_greenlet.switch(False) self.escape() - elif e in ['']: - self.request_greenlet.switch(False) - self.escape() else: # add normal character self.add_normal_character(e) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index e964109a0..190ea6fcb 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,4 +1,5 @@ import code +import time import traceback import sys from pygments.token import Generic, Token, Keyword, Name, Comment, String @@ -6,6 +7,7 @@ from pygments.token import Whitespace from pygments.formatter import Formatter from bpython.curtsiesfrontend.parse import parse +from bpython.repl import RuntimeTimer from codeop import CommandCompiler from pygments.lexers import get_lexer_by_name @@ -77,6 +79,7 @@ def __init__(self, locals=None): # typically changed after being instantiated self.write = lambda stuff: sys.stderr.write(stuff) self.outfile = self + self.timer = RuntimeTimer() def showsyntaxerror(self, filename=None): """Display the syntax error that just occurred. @@ -129,18 +132,18 @@ def showtraceback(self): tbtext = ''.join(l) lexer = get_lexer_by_name("pytb", stripall=True) - self.format(tbtext,lexer) + self.format(tbtext, lexer) def format(self, tbtext, lexer): traceback_informative_formatter = BPythonFormatter(default_colors) traceback_code_formatter = BPythonFormatter({Token: ('d')}) - tokens= list(lexer.get_tokens(tbtext)) + tokens = list(lexer.get_tokens(tbtext)) no_format_mode = False cur_line = [] for token, text in tokens: if text.endswith('\n'): - cur_line.append((token,text)) + cur_line.append((token, text)) if no_format_mode: traceback_code_formatter.format(cur_line, self.outfile) no_format_mode = False @@ -149,11 +152,17 @@ def format(self, tbtext, lexer): cur_line = [] elif text == ' ' and cur_line == []: no_format_mode = True - cur_line.append((token,text)) + cur_line.append((token, text)) else: - cur_line.append((token,text)) + cur_line.append((token, text)) assert cur_line == [], cur_line + def runsource(self, source, filename="", symbol="single"): + with self.timer: + return code.InteractiveInterpreter.runsource( + self, source, filename=filename, symbol=symbol) + + def code_finished_will_parse(s, compiler): """Returns a tuple of whether the buffer could be complete and whether it will parse diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f5d22f1bd..d2d16a8c0 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -219,6 +219,7 @@ def __init__(self, request_refresh=lambda: None, schedule_refresh=lambda when=0: None, request_reload=lambda desc: None, + request_undo=lambda n=1: None, get_term_hw=lambda:(50, 10), get_cursor_vertical_diff=lambda: 0, banner=None, @@ -282,6 +283,7 @@ def smarter_request_reload(files_modified=()): else: pass self.request_reload = smarter_request_reload + self.request_undo = request_undo self.get_term_hw = get_term_hw self.get_cursor_vertical_diff = get_cursor_vertical_diff @@ -469,6 +471,9 @@ def process_control_event(self, e): except IOError as e: self.status_bar.message(_('Executing PYTHONSTARTUP failed: %s') % (str(e))) + elif isinstance(e, bpythonevents.UndoEvent): + self.undo(n=e.n) + elif self.stdin.has_focus: return self.stdin.process_event(e) @@ -540,7 +545,7 @@ def process_key_event(self, e): elif e in ("",): self.on_tab(back=True) elif e in key_dispatch[self.config.undo_key]: #ctrl-r for undo - self.undo() + self.prompt_undo() elif e in key_dispatch[self.config.save_key]: # ctrl-s for save greenlet.greenlet(self.write2file).switch() elif e in key_dispatch[self.config.pastebin_key]: # F8 for pastebin @@ -1293,6 +1298,13 @@ def take_back_buffer_line(self): self._current_line = indent * ' ' self.cursor_offset = len(self.current_line) + def prompt_undo(self): + def prompt_for_undo(): + n = BpythonRepl.prompt_undo(self) + if n > 0: + self.request_undo(n=n) + greenlet.greenlet(prompt_for_undo).switch() + def reevaluate(self, insert_into_history=False): """bpython.Repl.undo calls this""" if self.watcher: self.watcher.reset() diff --git a/bpython/repl.py b/bpython/repl.py index 1fd1aef86..b180e325e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -34,6 +34,7 @@ import sys import tempfile import textwrap +import time import traceback import unicodedata from itertools import takewhile @@ -53,6 +54,25 @@ import bpython.autocomplete as autocomplete +class RuntimeTimer(object): + def __init__(self): + self.reset_timer() + + def __enter__(self): + self.start = time.time() + + def __exit__(self, ty, val, tb): + self.last_command = time.time() - self.start + self.running_time += self.last_command + return False + + def reset_timer(self): + self.running_time = 0.0 + self.last_command = 0.0 + + def estimate(self): + return self.running_time - self.last_command + class Interpreter(code.InteractiveInterpreter): def __init__(self, locals=None, encoding=None): @@ -68,16 +88,28 @@ def __init__(self, locals=None, encoding=None): self.syntaxerror_callback = None # Unfortunately code.InteractiveInterpreter is a classic class, so no super() code.InteractiveInterpreter.__init__(self, locals) + self.timer = RuntimeTimer() + + def reset_running_time(self): + self.running_time = 0 + + if py3: - if not py3: + def runsource(self, source, filename="", symbol="single"): + with self.timer: + return code.InteractiveInterpreter.runsource(self, source, + filename, symbol) + + else: def runsource(self, source, filename='', symbol='single', encode=True): - if encode: - source = '# coding: %s\n%s' % (self.encoding, - source.encode(self.encoding)) - return code.InteractiveInterpreter.runsource(self, source, - filename, symbol) + with self.timer: + if encode: + source = '# coding: %s\n%s' % (self.encoding, + source.encode(self.encoding)) + return code.InteractiveInterpreter.runsource(self, source, + filename, symbol) def showsyntaxerror(self, filename=None): """Override the regular handler, the code's copied and pasted from @@ -791,14 +823,44 @@ def insert_into_history(self, s): except RuntimeError as e: self.interact.notify(str(e)) + def prompt_undo(self): + """Returns how many lines to undo, 0 means don't undo""" + self.config.single_undo_time = 1 + if self.interp.timer.estimate() < self.config.single_undo_time: + return 1 + est = self.interp.timer.estimate() + n = self.interact.file_prompt( + _("Undo how many lines? (Undo will take up to ~%.1f seconds) [1]") + % (est,)) + try: + if n == '': + n = '1' + n = int(n) + except ValueError: + self.interact.notify(_('Undo canceled'), .1) + return 0 + else: + if n == 0: + self.interact.notify(_('Undo canceled'), .1) + return 0 + if n == 1: + self.interact.notify(_('Undoing 1 line... (est. %.1f seconds)') + % (est,), .1) + else: + self.interact.notify(_('Undoing %d lines... (est. %.1f seconds)') + % (n, est), .1) + return n + def undo(self, n=1): - """Go back in the undo history n steps and call reeavluate() + """Go back in the undo history n steps and call reevaluate() Note that in the program this is called "Rewind" because I want it to be clear that this is by no means a true undo implementation, it is merely a convenience bonus.""" if not self.history: return None + self.interp.timer.reset_timer() + if len(self.history) < n: n = len(self.history) From 7b19699abc5062d5328781385837b48fd65d2123 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 25 Jan 2015 16:56:56 -0500 Subject: [PATCH 0289/1650] add config option for single_undo_time --- bpython/config.py | 2 ++ bpython/repl.py | 4 ++-- bpython/sample-config | 5 +++++ doc/sphinx/source/configuration-options.rst | 9 +++++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index a8cfd0578..29b145719 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -53,6 +53,7 @@ def loadini(struct, configfile): 'hist_length': 100, 'hist_duplicates': True, 'paste_time': 0.02, + 'single_undo_time': 1.0, 'syntax': True, 'tab_length': 4, 'pastebin_confirm': True, @@ -141,6 +142,7 @@ def get_key_no_doublebind(command): struct.syntax = config.getboolean('general', 'syntax') struct.arg_spec = config.getboolean('general', 'arg_spec') struct.paste_time = config.getfloat('general', 'paste_time') + struct.single_undo_time = config.getfloat('general', 'single_undo_time') struct.highlight_show_source = config.getboolean('general', 'highlight_show_source') struct.hist_file = config.get('general', 'hist_file') diff --git a/bpython/repl.py b/bpython/repl.py index b180e325e..18a23daf7 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -825,8 +825,8 @@ def insert_into_history(self, s): def prompt_undo(self): """Returns how many lines to undo, 0 means don't undo""" - self.config.single_undo_time = 1 - if self.interp.timer.estimate() < self.config.single_undo_time: + if (self.config.single_undo_time < 0 or + self.interp.timer.estimate() < self.config.single_undo_time): return 1 est = self.interp.timer.estimate() n = self.interact.file_prompt( diff --git a/bpython/sample-config b/bpython/sample-config index 46c3460d9..3fe3f2f87 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -48,6 +48,11 @@ # bpython’s behalf. If unset, bpython uploads pastes to bpaste.net. (default: ) #pastebin_helper = gist.py +# How long an undo must be expected to take before prompting for how +# many lines should be undone. Set to -1 to never prompt, or 0 to +# always prompt. +# single_undo_time = 1.0 + [keyboard] # All key bindings are shown commented out with their default binding diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index b18358914..30a696170 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -120,6 +120,15 @@ following helper program can be used to create `gists .. versionadded:: 0.12 + +single_undo_time +^^^^^^^^^^^^^^^^ +Time duration an undo must be predicted to take before prompting +to undo multiple lines at once. Use -1 to never prompt, or 0 to always prompt. +(default: 1.0) + +.. versionadded:: 0.14 + .. _configuration_color_scheme: color_scheme From 2d9039cc73eec9b8b0e0d7ab28364d113fcf0924 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 25 Jan 2015 16:59:25 -0500 Subject: [PATCH 0290/1650] clean up whitespace in config --- bpython/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index a8cfd0578..45bf089cf 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -44,7 +44,7 @@ def loadini(struct, configfile): 'arg_spec': True, 'auto_display_list': True, 'color_scheme': 'default', - 'complete_magic_methods' : True, + 'complete_magic_methods': True, 'autocomplete_mode': default_completion, 'dedent_after': 1, 'flush_output': True, @@ -100,8 +100,8 @@ def loadini(struct, configfile): 'trim_prompts': False, }, 'curtsies': { - 'list_above' : False, - 'right_arrow_completion' : True, + 'list_above': False, + 'right_arrow_completion': True, }} default_keys_to_commands = dict((value, key) for (key, value) From 408260dff7a2261dc9e26c0cacdc12936a03d27b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 25 Jan 2015 17:52:45 -0500 Subject: [PATCH 0291/1650] use monotonically increasing time if available for undo time estimate --- bpython/repl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 18a23daf7..28cf30ab0 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -57,12 +57,13 @@ class RuntimeTimer(object): def __init__(self): self.reset_timer() + self.time = time.monotonic if hasattr(time, 'monotonic') else time.time def __enter__(self): - self.start = time.time() + self.start = self.time() def __exit__(self, ty, val, tb): - self.last_command = time.time() - self.start + self.last_command = self.time() - self.start self.running_time += self.last_command return False From 23321f24b582a919bdaadb8423d645048d86358a Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 25 Jan 2015 18:48:46 -0500 Subject: [PATCH 0292/1650] fix #465 and add IPython-style py3compat cast functions --- bpython/_py3compat.py | 13 +++++++++++++ bpython/curtsiesfrontend/repl.py | 13 +++++++++---- bpython/repl.py | 2 +- bpython/test/test_curtsies_repl.py | 23 ++++++++++++++++++++++- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index dd3c489fd..3894a6a28 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -35,6 +35,7 @@ """ import sys +import locale py3 = (sys.version_info[0] == 3) @@ -42,3 +43,15 @@ from pygments.lexers import Python3Lexer as PythonLexer else: from pygments.lexers import PythonLexer + + +def cast_unicode(s, encoding=locale.getpreferredencoding()): + if isinstance(s, bytes): + return s.decode(encoding) + return s + + +def cast_bytes(s, encoding=locale.getpreferredencoding()): + if not isinstance(s, bytes): + return s.encode(encoding) + return s diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f5d22f1bd..5d0a0f796 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -14,7 +14,7 @@ import unicodedata from pygments import format -from bpython._py3compat import PythonLexer +from bpython._py3compat import PythonLexer, cast_unicode, cast_bytes from pygments.formatters import TerminalFormatter import blessings @@ -1362,6 +1362,9 @@ def focus_on_subprocess(self, args): signal.signal(signal.SIGWINCH, prev_sigwinch_handler) def pager(self, text): + """Runs an external pager on text + + text must be a bytestring, ie not yet encoded""" command = get_pager_command() with tempfile.NamedTemporaryFile() as tmp: tmp.write(text) @@ -1375,12 +1378,14 @@ def show_source(self): self.status_bar.message(str(e)) else: if self.config.highlight_show_source: - source = format(PythonLexer().get_tokens(source), - TerminalFormatter()) + source = cast_bytes(format(PythonLexer().get_tokens(source), + TerminalFormatter())) + else: + source = cast_bytes(source) self.pager(source) def help_text(self): - return (self.version_help_text() + '\n' + self.key_help_text()).encode('utf8') + return cast_bytes(self.version_help_text() + '\n' + self.key_help_text()) def version_help_text(self): return (('bpython-curtsies version %s' % bpython.__version__) + ' ' + diff --git a/bpython/repl.py b/bpython/repl.py index 1fd1aef86..240489f06 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -468,7 +468,7 @@ def get_args(self): def get_source_of_current_name(self): """Return the source code of the object which is bound to the current name in the current input line. Throw `SourceNotFound` if the - source cannot be found.""" + source cannot be found. Returns bytestring in py2, unicode in py3.""" obj = self.current_func try: if obj is None: diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 2c9b8d67c..4cfac5f72 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -206,7 +206,7 @@ def captured_output(): sys.stdout, sys.stderr = old_out, old_err def create_repl(**kwargs): - config = setup_config({'editor':'true'}) + config = setup_config({'editor': 'true'}) repl = curtsiesrepl.Repl(config=config, **kwargs) os.environ['PAGER'] = 'true' repl.width = 50 @@ -264,6 +264,27 @@ def test_variable_is_cleared(self): self.repl.undo() self.assertNotIn('b', self.repl.interp.locals) +class TestCurtsiesPagerText(unittest.TestCase): + + def setUp(self): + self.repl = create_repl() + self.repl.pager = self.assert_pager_gets_bytes + + def assert_pager_gets_bytes(self, text): + self.assertIsInstance(text, type(b'')) + + def test_help(self): + self.repl.pager(self.repl.help_text()) + + def test_show_source_not_formatted(self): + self.repl.config.highlight_show_source = False + self.repl.get_source_of_current_name = lambda: 'source code å∂߃åß∂ƒ' + self.repl.show_source() + + def test_show_source_formatted(self): + self.repl.config.highlight_show_source = True + self.repl.get_source_of_current_name = lambda: 'source code å∂߃åß∂ƒ' + self.repl.show_source() if __name__ == '__main__': unittest.main() From ba14a3e747d36b29ca32290c7e8faa3b31ceaceb Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 25 Jan 2015 18:54:28 -0500 Subject: [PATCH 0293/1650] prevent "may be a binary file" message with default pager --- bpython/pager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/pager.py b/bpython/pager.py index 1a99e8e57..7133f598f 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -29,7 +29,7 @@ import sys import shlex -def get_pager_command(default='less -r'): +def get_pager_command(default='less -rf'): command = shlex.split(os.environ.get('PAGER', default)) return command From 327b4c916540ec0151670bae12b3fd74e2246e65 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Jan 2015 10:31:53 +0100 Subject: [PATCH 0294/1650] Remove obsolete workarounds Signed-off-by: Sebastian Ramacher --- bpython/importcompletion.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 125a35c77..efcca1c1d 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -20,13 +20,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import with_statement - +from bpython._py3compat import py3 from bpython import line as lineparts import imp import os import sys import warnings +from warnings import catch_warnings if sys.version_info[0] == 3 and sys.version_info[1] >= 3: import importlib.machinery @@ -34,23 +34,6 @@ else: SUFFIXES = [suffix for suffix, mode, type in imp.get_suffixes()] -try: - from warnings import catch_warnings -except ImportError: - import contextlib - @contextlib.contextmanager - def catch_warnings(): - """Stripped-down version of `warnings.catch_warnings()` - (available in Py >= 2.6).""" - filters = warnings.filters - warnings.filters = list(filters) - try: - yield - finally: - warnings.filters = filters - -from bpython._py3compat import py3 - # The cached list of all known modules modules = set() fully_loaded = False From 648d0b200ae896c3384e576b6df8d2b00d622f21 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Jan 2015 10:34:28 +0100 Subject: [PATCH 0295/1650] Use _py3compat Signed-off-by: Sebastian Ramacher --- bpython/translations/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index f3039e52f..b98dcea4f 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -4,10 +4,11 @@ import sys from bpython import package_dir +from bpython._py3compat import py3 translator = None -if sys.version_info >= (3, 0): +if py3: def _(message): return translator.gettext(message) else: From 71955bdc5ebeabd5c5a31d035a18e8a4f861542e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 26 Jan 2015 20:06:44 -0500 Subject: [PATCH 0296/1650] prevent hide paren from accessing nonexistant lines --- bpython/curtsiesfrontend/repl.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 5d0a0f796..d568856f5 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -673,14 +673,16 @@ def yank_from_buffer(self): def up_one_line(self): self.rl_history.enter(self.current_line) self._set_current_line(tabs_to_spaces(self.rl_history.back(False, - search=self.config.curtsies_right_arrow_completion)), + search=self.config.curtsies_right_arrow_completion)), + update_completion=False, reset_rl_history=False) self._set_cursor_offset(len(self.current_line), reset_rl_history=False) def down_one_line(self): self.rl_history.enter(self.current_line) self._set_current_line(tabs_to_spaces(self.rl_history.forward(False, - search=self.config.curtsies_right_arrow_completion)), + search=self.config.curtsies_right_arrow_completion)), + update_completion=False, reset_rl_history=False) self._set_cursor_offset(len(self.current_line), reset_rl_history=False) @@ -838,10 +840,10 @@ def push(self, line, insert_into_history=True): self.saved_predicted_parse_error = not code_will_parse if c: logger.debug('finished - buffer cleared') + self.cursor_offset = 0 self.display_lines.extend(self.display_buffer_lines) self.display_buffer = [] self.buffer = [] - self.cursor_offset = 0 self.coderunner.load_code(code_to_run) self.run_code_and_maybe_finish() From 9ce515f2edb2fdade268c8a2142006b15b1e816f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 26 Jan 2015 20:34:33 -0500 Subject: [PATCH 0297/1650] fix remove paren in take_back_buffer_line --- bpython/curtsiesfrontend/repl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d568856f5..4071e8545 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1283,10 +1283,8 @@ def reprint_line(self, lineno, tokens): self.display_buffer[lineno] = bpythonparse(format(tokens, self.formatter)) def take_back_buffer_line(self): - self.display_buffer.pop() - self.buffer.pop() - - if not self.buffer: + assert len(self.buffer) > 0 + if len(self.buffer) == 1: self._cursor_offset = 0 self.current_line = '' else: @@ -1294,6 +1292,8 @@ def take_back_buffer_line(self): indent = self.predicted_indent(line) self._current_line = indent * ' ' self.cursor_offset = len(self.current_line) + self.display_buffer.pop() + self.buffer.pop() def reevaluate(self, insert_into_history=False): """bpython.Repl.undo calls this""" From 2f4c0186a736b3b77b6ce8176dffb20edfc81060 Mon Sep 17 00:00:00 2001 From: Tarek Ziade Date: Wed, 28 Jan 2015 22:47:28 +0100 Subject: [PATCH 0298/1650] first shot at the french translation --- .../translations/fr_FR/LC_MESSAGES/bpython.po | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 bpython/translations/fr_FR/LC_MESSAGES/bpython.po diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po new file mode 100644 index 000000000..14573c63f --- /dev/null +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -0,0 +1,211 @@ +# French (France) translations for bpython. +# Copyright (C) 2010 bpython developers +# This file is distributed under the same license as the bpython project. +# +msgid "" +msgstr "" +"Project-Id-Version: bpython 0.13-442\n" +"Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" +"POT-Creation-Date: 2015-01-23 23:19+0100\n" +"Last-Translator: Tarek Ziadé \n" +"Language-Team: bpython developers\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 1.3\n" + +#: bpython/args.py:57 +msgid "" +"Usage: %prog [options] [file [args]]\n" +"NOTE: If bpython sees an argument it does not know, execution falls back " +"to the regular Python interpreter." +msgstr "" + +#: bpython/args.py:67 +msgid "Use CONFIG instead of default config file." +msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." + +#: bpython/args.py:69 +msgid "Drop to bpython shell after running file instead of exiting." +msgstr "Aller dans le shell bpython après l'exécution du fichier au lieu de quitter." + +#: bpython/args.py:72 +msgid "Don't flush the output to stdout." +msgstr "Ne pas purger la sortie vers stdout." + +#: bpython/args.py:74 +msgid "Print version and exit." +msgstr "Afficher la version et quitter." + +#: bpython/cli.py:321 bpython/urwid.py:555 +msgid "y" +msgstr "o" + +#: bpython/cli.py:321 bpython/urwid.py:555 +msgid "yes" +msgstr "oui" + +#: bpython/cli.py:1696 +msgid "Rewind" +msgstr "Rembobiner" + +#: bpython/cli.py:1697 +msgid "Save" +msgstr "Sauvegarder" + +#: bpython/cli.py:1698 +msgid "Pastebin" +msgstr "" + +#: bpython/cli.py:1699 +msgid "Pager" +msgstr "" + +#: bpython/cli.py:1700 +msgid "Show Source" +msgstr "Montrer le code source" + +#: bpython/curtsies.py:31 +msgid "log debug messages to bpython.log" +msgstr "logger les messages de debug dans bpython.log" + +#: bpython/curtsies.py:33 +msgid "enter lines of file as though interactively typed" +msgstr "saisir les lignes du fichier interactivement" + +#: bpython/history.py:236 +#, python-format +msgid "Error occurded while writing to file %s (%s)" +msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" + +#: bpython/repl.py:477 +msgid "Nothing to get source of" +msgstr "" + +#: bpython/repl.py:482 +#, python-format +msgid "Cannot get source: %s" +msgstr "Impossible de récupérer le source: %s" + +#: bpython/repl.py:487 +#, python-format +msgid "Cannot access source of %r" +msgstr "Impossible d'accéder au source de %r" + +#: bpython/repl.py:489 +#, python-format +msgid "No source code found for %s" +msgstr "Pas de code source trouvé pour %s" + +#: bpython/repl.py:655 +msgid "No clipboard available." +msgstr "Pas de presse-papier disponible." + +#: bpython/repl.py:662 +msgid "Could not copy to clipboard." +msgstr "Impossible de copier vers le presse-papier." + +#: bpython/repl.py:664 +msgid "Copied content to clipboard." +msgstr "Contenu copié vers le presse-papier." + +#: bpython/repl.py:673 +msgid "Pastebin buffer? (y/N) " +msgstr "Tampon Pastebin ? (o/N) " + +#: bpython/repl.py:674 +msgid "Pastebin aborted" +msgstr "Pastebin abandonné" + +#: bpython/repl.py:681 +#, python-format +msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" +msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" + +#: bpython/repl.py:700 bpython/repl.py:728 +msgid "Posting data to pastebin..." +msgstr "Envoi des donnés à pastebin..." + +#: bpython/repl.py:705 +#, python-format +msgid "Upload failed: %s" +msgstr "Echec du téléchargement: %s" + +#: bpython/repl.py:721 +#, python-format +msgid "Pastebin URL: %s - Removal URL: %s" +msgstr "URL Pastebin: %s - URL de suppression: %s" + +#: bpython/repl.py:740 +msgid "Upload failed: Helper program not found." +msgstr "Echec de l'upload: programme externe non trouvé." + +#: bpython/repl.py:743 +msgid "Upload failed: Helper program could not be run." +msgstr "Echec de l'upload: impossible de lancer le programme externe." + +#: bpython/repl.py:750 +#, python-format +msgid "Upload failed: Helper program returned non-zero exit status %s." +msgstr "Echec de l'upload: le programme externe a renvoyé un statut de sortie différent de zéro %s." + +#: bpython/repl.py:754 +msgid "Upload failed: No output from helper program." +msgstr "Echec de l'upload: pas de sortie du programme externe." + +#: bpython/repl.py:761 +msgid "Upload failed: Failed to recognize the helper program's output as an URL." +msgstr "Echec de l'upload: la sortie du programme externe ne correspond pas à une URL." + +#: bpython/repl.py:767 +#, python-format +msgid "Pastebin URL: %s" +msgstr "URL Pastebin: %s" + +#: bpython/repl.py:953 +msgid "Config file does not exist - create new from default? (y/N)" +msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" + +#: bpython/urwid.py:617 +#, python-format +msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " +msgstr " <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> Montrer Source " + +#: bpython/urwid.py:1126 +msgid "Run twisted reactor." +msgstr "Lancer le reactor twisted." + +#: bpython/urwid.py:1128 +msgid "Select specific reactor (see --help-reactors). Implies --twisted." +msgstr "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." + +#: bpython/urwid.py:1131 +msgid "List available reactors for -r." +msgstr "Lister les reactors disponibles pour -r." + +#: bpython/urwid.py:1133 +msgid "" +"twistd plugin to run (use twistd for a list). Use \"--\" to pass further " +"options to the plugin." +msgstr "" +"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" pour donner " +"plus d'options au plugin." + +#: bpython/urwid.py:1136 +msgid "Port to run an eval server on (forces Twisted)." +msgstr "Port pour lancer un server eval (force Twisted)." + +#: bpython/curtsiesfrontend/repl.py:258 +msgid "Welcome to bpython!" +msgstr "Bienvenue dans bpython!" + +#: bpython/curtsiesfrontend/repl.py:258 +#, python-format +msgid "Press <%s> for help." +msgstr "Appuyer sur <%s> pour de l'aide." + +#: bpython/curtsiesfrontend/repl.py:470 +#, python-format +msgid "Executing PYTHONSTARTUP failed: %s" +msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" From 22bbdea99b7f3f8fd521341b7b839b7f1da2faa4 Mon Sep 17 00:00:00 2001 From: Tarek Ziade Date: Wed, 28 Jan 2015 22:48:56 +0100 Subject: [PATCH 0299/1650] fixed typo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 55297abc7..f5ec49a1a 100755 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ # version handling version_file = 'bpython/_version.py' -version = 'unkown' +version = 'unknown' try: # get version from git describe From 88e8961431371946ab3081519f192af64695bda4 Mon Sep 17 00:00:00 2001 From: Tarek Ziade Date: Wed, 28 Jan 2015 22:51:29 +0100 Subject: [PATCH 0300/1650] fixed another typo --- bpython/history.py | 2 +- bpython/translations/bpython.pot | 2 +- bpython/translations/de/LC_MESSAGES/bpython.po | 2 +- bpython/translations/es_ES/LC_MESSAGES/bpython.po | 2 +- bpython/translations/fr_FR/LC_MESSAGES/bpython.po | 2 +- bpython/translations/it_IT/LC_MESSAGES/bpython.po | 2 +- bpython/translations/nl_NL/LC_MESSAGES/bpython.po | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bpython/history.py b/bpython/history.py index 1b1de2926..c62746d1d 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -233,7 +233,7 @@ def append_reload_and_write(self, s, filename, encoding): self.entries = entries except EnvironmentError as err: - raise RuntimeError(_('Error occurded while writing to file %s (%s)') + raise RuntimeError(_('Error occured while writing to file %s (%s)') % (filename, err.strerror)) else: if len(self.entries) == 0: diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 118584890..3ea9539d8 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -78,7 +78,7 @@ msgstr "" #: bpython/history.py:236 #, python-format -msgid "Error occurded while writing to file %s (%s)" +msgid "Error occured while writing to file %s (%s)" msgstr "" #: bpython/repl.py:477 diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 99ccc970a..8936b2dcf 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -78,7 +78,7 @@ msgstr "" #: bpython/history.py:236 #, python-format -msgid "Error occurded while writing to file %s (%s)" +msgid "Error occured while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" #: bpython/repl.py:477 diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 9df0ebad4..bb5ad9a29 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -78,7 +78,7 @@ msgstr "" #: bpython/history.py:236 #, python-format -msgid "Error occurded while writing to file %s (%s)" +msgid "Error occured while writing to file %s (%s)" msgstr "" #: bpython/repl.py:477 diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index 14573c63f..cd33d3426 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -76,7 +76,7 @@ msgstr "saisir les lignes du fichier interactivement" #: bpython/history.py:236 #, python-format -msgid "Error occurded while writing to file %s (%s)" +msgid "Error occured while writing to file %s (%s)" msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" #: bpython/repl.py:477 diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index 0fa791916..c86247b35 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -78,7 +78,7 @@ msgstr "" #: bpython/history.py:236 #, python-format -msgid "Error occurded while writing to file %s (%s)" +msgid "Error occured while writing to file %s (%s)" msgstr "" #: bpython/repl.py:477 diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index da7519cde..7b7d70a61 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -78,7 +78,7 @@ msgstr "" #: bpython/history.py:236 #, python-format -msgid "Error occurded while writing to file %s (%s)" +msgid "Error occured while writing to file %s (%s)" msgstr "" #: bpython/repl.py:477 From 0fb6ee55a1c71d9ee59c391ee05868f3e45d9da7 Mon Sep 17 00:00:00 2001 From: Tarek Ziade Date: Wed, 28 Jan 2015 22:54:47 +0100 Subject: [PATCH 0301/1650] one more translation for optparse --- bpython/translations/fr_FR/LC_MESSAGES/bpython.po | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index cd33d3426..cf1523ad8 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -21,6 +21,9 @@ msgid "" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" +"Utilisation: %prog [options] [fichier [arguments]]\n" +"NOTE: Si bpython ne reconnaît pas un des arguments fournis, " +"l'interpréteur Python classique sera lancé" #: bpython/args.py:67 msgid "Use CONFIG instead of default config file." From 3b8a35d4e6bee7199a29e1be01ad19bb20f6344e Mon Sep 17 00:00:00 2001 From: Tarek Ziade Date: Wed, 28 Jan 2015 23:02:22 +0100 Subject: [PATCH 0302/1650] the right spelling is occurred --- bpython/cli.py | 2 +- bpython/history.py | 2 +- bpython/translations/bpython.pot | 2 +- bpython/translations/de/LC_MESSAGES/bpython.po | 2 +- bpython/translations/es_ES/LC_MESSAGES/bpython.po | 2 +- bpython/translations/fr_FR/LC_MESSAGES/bpython.po | 2 +- bpython/translations/it_IT/LC_MESSAGES/bpython.po | 2 +- bpython/translations/nl_NL/LC_MESSAGES/bpython.po | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index c014f3e13..f18355d17 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -436,7 +436,7 @@ def check(self): self.print_line(self.s) def clear_current_line(self): - """Called when a SyntaxError occured in the interpreter. It is + """Called when a SyntaxError occurred in the interpreter. It is used to prevent autoindentation from occuring after a traceback.""" repl.Repl.clear_current_line(self) diff --git a/bpython/history.py b/bpython/history.py index c62746d1d..8f795619e 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -233,7 +233,7 @@ def append_reload_and_write(self, s, filename, encoding): self.entries = entries except EnvironmentError as err: - raise RuntimeError(_('Error occured while writing to file %s (%s)') + raise RuntimeError(_('Error occurred while writing to file %s (%s)') % (filename, err.strerror)) else: if len(self.entries) == 0: diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 3ea9539d8..0685dc0ae 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -78,7 +78,7 @@ msgstr "" #: bpython/history.py:236 #, python-format -msgid "Error occured while writing to file %s (%s)" +msgid "Error occurred while writing to file %s (%s)" msgstr "" #: bpython/repl.py:477 diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 8936b2dcf..9b64ef25c 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -78,7 +78,7 @@ msgstr "" #: bpython/history.py:236 #, python-format -msgid "Error occured while writing to file %s (%s)" +msgid "Error occurred while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" #: bpython/repl.py:477 diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index bb5ad9a29..949666af2 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -78,7 +78,7 @@ msgstr "" #: bpython/history.py:236 #, python-format -msgid "Error occured while writing to file %s (%s)" +msgid "Error occurred while writing to file %s (%s)" msgstr "" #: bpython/repl.py:477 diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index cf1523ad8..69a7f127f 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -79,7 +79,7 @@ msgstr "saisir les lignes du fichier interactivement" #: bpython/history.py:236 #, python-format -msgid "Error occured while writing to file %s (%s)" +msgid "Error occurred while writing to file %s (%s)" msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" #: bpython/repl.py:477 diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index c86247b35..35a2ab832 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -78,7 +78,7 @@ msgstr "" #: bpython/history.py:236 #, python-format -msgid "Error occured while writing to file %s (%s)" +msgid "Error occurred while writing to file %s (%s)" msgstr "" #: bpython/repl.py:477 diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index 7b7d70a61..04e31bd23 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -78,7 +78,7 @@ msgstr "" #: bpython/history.py:236 #, python-format -msgid "Error occured while writing to file %s (%s)" +msgid "Error occurred while writing to file %s (%s)" msgstr "" #: bpython/repl.py:477 From 0811bdd5ed32c12ea9c085dbe6a6da9379b63b9f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 28 Jan 2015 20:06:29 -0500 Subject: [PATCH 0303/1650] don't use undo estimate if taking back line of multiline input --- bpython/curtsiesfrontend/repl.py | 7 +++++++ bpython/repl.py | 6 +----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d2d16a8c0..f6611bd86 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1286,6 +1286,7 @@ def reprint_line(self, lineno, tokens): self.display_buffer[lineno] = bpythonparse(format(tokens, self.formatter)) def take_back_buffer_line(self): + self.history = self.history[:-1] self.display_buffer.pop() self.buffer.pop() @@ -1299,10 +1300,16 @@ def take_back_buffer_line(self): self.cursor_offset = len(self.current_line) def prompt_undo(self): + if self.buffer: + return self.take_back_buffer_line() + + self.reevaluate() + def prompt_for_undo(): n = BpythonRepl.prompt_undo(self) if n > 0: self.request_undo(n=n) + greenlet.greenlet(prompt_for_undo).switch() def reevaluate(self, insert_into_history=False): diff --git a/bpython/repl.py b/bpython/repl.py index 28cf30ab0..975021c74 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -868,11 +868,7 @@ def undo(self, n=1): entries = list(self.rl_history.entries) self.history = self.history[:-n] - if (n == 1 and self.buffer and - hasattr(self, 'take_back_buffer_line')): - self.take_back_buffer_line() - else: - self.reevaluate() + self.reevaluate() self.rl_history.entries = entries From b23e1915a5fac50898e3b82ac6a45ddceb2ffc46 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 28 Jan 2015 20:55:30 -0500 Subject: [PATCH 0304/1650] get source in unicode in python 2 and 3 --- bpython/inspection.py | 15 +++++++++++++++ bpython/repl.py | 7 ++++--- bpython/test/test_inspection.py | 6 ++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 0c7f14cc9..c7f81accc 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -270,3 +270,18 @@ def is_callable(obj): return isinstance(obj, collections.Callable) else: return callable(obj) + + +def get_encoding(obj): + for line in inspect.getsourcelines(obj)[0][:2]: + m = re.search(r'coding[:=]\s*([-\w.]+)', line) + if m: + return m.group(1) + return 'ascii' + + +def get_source_unicode(obj): + """Returns a decoded source of object""" + if py3: + return inspect.getsource(obj) + return inspect.getsource(obj).decode(get_encoding(obj)) diff --git a/bpython/repl.py b/bpython/repl.py index 240489f06..a5c46b713 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -466,9 +466,10 @@ def get_args(self): return False def get_source_of_current_name(self): - """Return the source code of the object which is bound to the + """Return the unicode source code of the object which is bound to the current name in the current input line. Throw `SourceNotFound` if the - source cannot be found. Returns bytestring in py2, unicode in py3.""" + source cannot be found.""" + obj = self.current_func try: if obj is None: @@ -477,7 +478,7 @@ def get_source_of_current_name(self): raise SourceNotFound(_("Nothing to get source of")) if inspection.is_eval_safe_name(line): obj = self.get_object(line) - return inspect.getsource(obj) + return inspection.get_source_unicode(obj) except (AttributeError, NameError) as e: msg = _("Cannot get source: %s") % (str(e), ) except IOError as e: diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 9b3edd1cc..0e428cc85 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -61,5 +61,11 @@ def spam(eggs=23, foobar="yay"): self.assertEqual(repr(defaults[0]), "23") self.assertEqual(repr(defaults[1]), "'yay'") + def test_get_encoding(self): + self.assertEqual(inspection.get_encoding(inspection), 'ascii') + from bpython.test import test_curtsies_painting + self.assertEqual(inspection.get_encoding(test_curtsies_painting), 'utf8') + + if __name__ == '__main__': unittest.main() From 4773da6a39e4833ceb944d01083c9508140b2e62 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 28 Jan 2015 21:32:12 -0500 Subject: [PATCH 0305/1650] fix get_encoding in inspection --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index c7f81accc..02be5a415 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -273,7 +273,7 @@ def is_callable(obj): def get_encoding(obj): - for line in inspect.getsourcelines(obj)[0][:2]: + for line in inspect.findsource(obj)[0][:2]: m = re.search(r'coding[:=]\s*([-\w.]+)', line) if m: return m.group(1) From 26c147df7abab2a60bc62a5bcd2b87722c1d9c58 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 28 Jan 2015 12:58:48 +0100 Subject: [PATCH 0306/1650] Make pep8 happy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … and use ifilter Signed-off-by: Sebastian Ramacher --- bpython/config.py | 49 ++++++++++++++++++++----------------- bpython/history.py | 36 ++++++--------------------- bpython/importcompletion.py | 34 ++++++++++++++----------- 3 files changed, 54 insertions(+), 65 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 735f1d415..e247f0524 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -10,19 +10,23 @@ import bpython.autocomplete + class Struct(object): """Simple class for instantiating objects we can add arbitrary attributes to and use for various arbitrary things.""" + def get_config_home(): """Returns the base directory for bpython's configuration files.""" xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config') return os.path.join(xdg_config_home, 'bpython') + def default_config_path(): """Returns bpython's default configuration file path.""" return os.path.join(get_config_home(), 'config') + def fill_config_with_default_values(config, default_values): for section in default_values.iterkeys(): if not config.has_section(section): @@ -117,7 +121,6 @@ def loadini(struct, configfile): "%s\n" % default_config_path()) sys.exit(1) - def get_key_no_doublebind(command): default_commands_to_keys = defaults['keyboard'] requested_key = config.get('keyboard', command) @@ -126,7 +129,7 @@ def get_key_no_doublebind(command): default_command = default_keys_to_commands[requested_key] if default_commands_to_keys[default_command] == \ - config.get('keyboard', default_command): + config.get('keyboard', default_command): setattr(struct, '%s_key' % default_command, '') except KeyError: pass @@ -194,7 +197,7 @@ def get_key_no_doublebind(command): struct.cli_suggestion_width = config.getfloat('cli', 'suggestion_width') struct.cli_trim_prompts = config.getboolean('cli', - 'trim_prompts') + 'trim_prompts') struct.complete_magic_methods = config.getboolean('general', 'complete_magic_methods') @@ -202,29 +205,29 @@ def get_key_no_doublebind(command): struct.save_append_py = config.getboolean('general', 'save_append_py') struct.curtsies_list_above = config.getboolean('curtsies', 'list_above') - struct.curtsies_right_arrow_completion = config.getboolean('curtsies', - 'right_arrow_completion') + struct.curtsies_right_arrow_completion = \ + config.getboolean('curtsies', 'right_arrow_completion') color_scheme_name = config.get('general', 'color_scheme') default_colors = { - 'keyword': 'y', - 'name': 'c', - 'comment': 'b', - 'string': 'm', - 'error': 'r', - 'number': 'G', - 'operator': 'Y', - 'punctuation': 'y', - 'token': 'C', - 'background': 'd', - 'output': 'w', - 'main': 'c', - 'paren': 'R', - 'prompt': 'c', - 'prompt_more': 'g', - 'right_arrow_suggestion': 'K', - } + 'keyword': 'y', + 'name': 'c', + 'comment': 'b', + 'string': 'm', + 'error': 'r', + 'number': 'G', + 'operator': 'Y', + 'punctuation': 'y', + 'token': 'C', + 'background': 'd', + 'output': 'w', + 'main': 'c', + 'paren': 'R', + 'prompt': 'c', + 'prompt_more': 'g', + 'right_arrow_suggestion': 'K', + } if color_scheme_name == 'default': struct.color_scheme = default_colors @@ -238,7 +241,7 @@ def get_key_no_doublebind(command): load_theme(struct, path, struct.color_scheme, default_colors) except EnvironmentError: sys.stderr.write("Could not load theme '%s'.\n" % - (color_scheme_name, )) + (color_scheme_name, )) sys.exit(1) # checks for valid key configuration this part still sucks diff --git a/bpython/history.py b/bpython/history.py index 8f795619e..80952a9d8 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -37,18 +37,17 @@ def __init__(self, entries=None, duplicates=True, hist_size=100): self.entries = [''] else: self.entries = list(entries) - self.index = 0 # how many lines back in history is currently selected - # where 0 is the saved typed line, 1 the prev entered - # line - self.saved_line = '' # what was on the prompt before using history + # how many lines back in history is currently selected where 0 is the + # saved typed line, 1 the prev entered line + self.index = 0 + # what was on the prompt before using history + self.saved_line = '' self.duplicates = duplicates self.hist_size = hist_size - def append(self, line): self.append_to(self.entries, line) - def append_to(self, entries, line): line = line.rstrip('\n') if line: @@ -61,14 +60,12 @@ def append_to(self, entries, line): pass entries.append(line) - def first(self): """Move back to the beginning of the history.""" if not self.is_at_end: self.index = len(self.entries) return self.entries[-self.index] - def back(self, start=True, search=False, target=None, include_current=False): """Move one step back in the history.""" @@ -84,18 +81,15 @@ def back(self, start=True, search=False, target=None, self.index += 1 return self.entry - @property def entry(self): """The current entry, which may be the saved line""" return self.entries[-self.index] if self.index else self.saved_line - @property def entries_by_index(self): return list(reversed(self.entries + [self.saved_line])) - def find_match_backward(self, search_term, include_current=False): add = 0 if include_current else 1 start = self.index + add @@ -104,7 +98,6 @@ def find_match_backward(self, search_term, include_current=False): return idx + add return 0 - def find_partial_match_backward(self, search_term, include_current=False): add = 0 if include_current else 1 start = self.index + add @@ -113,7 +106,6 @@ def find_partial_match_backward(self, search_term, include_current=False): return idx + add return 0 - def forward(self, start=True, search=False, target=None, include_current=False): """Move one step forward in the history.""" @@ -132,7 +124,6 @@ def forward(self, start=True, search=False, target=None, self.index = 0 return self.saved_line - def find_match_forward(self, search_term, include_current=False): add = 0 if include_current else 1 end = max(0, self.index - (1 - add)) @@ -142,7 +133,6 @@ def find_match_forward(self, search_term, include_current=False): return idx + (0 if include_current else 1) return self.index - def find_partial_match_forward(self, search_term, include_current=False): add = 0 if include_current else 1 end = max(0, self.index - (1 - add)) @@ -152,60 +142,50 @@ def find_partial_match_forward(self, search_term, include_current=False): return idx + add return self.index - def last(self): """Move forward to the end of the history.""" if not self.is_at_start: self.index = 0 return self.entries[0] - @property def is_at_end(self): return self.index >= len(self.entries) or self.index == -1 - @property def is_at_start(self): return self.index == 0 - def enter(self, line): if self.index == 0: self.saved_line = line - @classmethod def from_filename(cls, filename): history = cls() history.load(filename) return history - def reset(self): self.index = 0 self.saved_line = '' - def load(self, filename, encoding): with codecs.open(filename, 'r', encoding, 'ignore') as hfile: with FileLock(hfile): self.entries = self.load_from(hfile) - def load_from(self, fd): entries = [] for line in fd: self.append_to(entries, line) return entries - def save(self, filename, encoding, lines=0): with codecs.open(filename, 'w', encoding, 'ignore') as hfile: with FileLock(hfile): self.save_to(hfile, self.entries, lines) - def save_to(self, fd, entries=None, lines=0): if entries is None: entries = self.entries @@ -213,7 +193,6 @@ def save_to(self, fd, entries=None, lines=0): fd.write(line) fd.write('\n') - def append_reload_and_write(self, s, filename, encoding): if not self.hist_size: return self.append(s) @@ -233,8 +212,9 @@ def append_reload_and_write(self, s, filename, encoding): self.entries = entries except EnvironmentError as err: - raise RuntimeError(_('Error occurred while writing to file %s (%s)') - % (filename, err.strerror)) + raise RuntimeError( + _('Error occurred while writing to file %s (%s)') + % (filename, err.strerror)) else: if len(self.entries) == 0: # Make sure that entries contains at least one element. If the diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index efcca1c1d..d7bb1bf96 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -21,12 +21,15 @@ # THE SOFTWARE. from bpython._py3compat import py3 -from bpython import line as lineparts +from bpython.line import current_word, current_import, \ + current_from_import_from, current_from_import_import + import imp import os import sys import warnings from warnings import catch_warnings +from itertools import ifilter if sys.version_info[0] == 3 and sys.version_info[1] >= 3: import importlib.machinery @@ -61,6 +64,7 @@ def module_matches(cw, prefix=''): else: return set(matches) + def attr_matches(cw, prefix='', only_modules=False): """Attributes to replace name with""" full = '%s.%s' % (prefix, cw) if prefix else cw @@ -79,31 +83,32 @@ def attr_matches(cw, prefix='', only_modules=False): if module_part: matches = ('%s.%s' % (module_part, m) for m in matches) - return set(filter(lambda x: x is not None, - (try_decode_module(match, 'ascii') for match in matches))) + generator = (try_decode_module(match, 'ascii') for match in matches) + return set(ifilter(lambda x: x is not None, generator)) + def module_attr_matches(name): """Only attributes which are modules to replace name with""" return attr_matches(name, prefix='', only_modules=True) + def complete(cursor_offset, line): """Construct a full list of possibly completions for imports.""" tokens = line.split() if 'from' not in tokens and 'import' not in tokens: return None - result = lineparts.current_word(cursor_offset, line) + result = current_word(cursor_offset, line) if result is None: return None - from_import_from = lineparts.current_from_import_from(cursor_offset, line) + from_import_from = current_from_import_from(cursor_offset, line) if from_import_from is not None: - from_import_import = lineparts.current_from_import_import(cursor_offset, - line) - if from_import_import is not None: + import_import = current_from_import_import(cursor_offset, line) + if import_import is not None: # `from a import ` completion - matches = module_matches(from_import_import[2], from_import_from[2]) - matches.update(attr_matches(from_import_import[2], + matches = module_matches(import_import[2], from_import_from[2]) + matches.update(attr_matches(import_import[2], from_import_from[2])) else: # `from ` completion @@ -111,15 +116,16 @@ def complete(cursor_offset, line): matches.update(module_matches(from_import_from[2])) return matches - current_import = lineparts.current_import(cursor_offset, line) - if current_import is not None: + cur_import = current_import(cursor_offset, line) + if cur_import is not None: # `import ` completion - matches = module_matches(current_import[2]) - matches.update(module_attr_matches(current_import[2])) + matches = module_matches(cur_import[2]) + matches.update(module_attr_matches(cur_import[2])) return matches else: return None + def find_modules(path): """Find all modules (and packages) for a given directory.""" if not os.path.isdir(path): From 80e7706f0fb9f583c5f85f0da2246aca2c1f477b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 29 Jan 2015 16:09:01 +0100 Subject: [PATCH 0307/1650] Use a consistent signature for matches Also includes some PEP-8 fixes Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 116 +++++++++++++++++++++--------- bpython/test/test_autocomplete.py | 33 +++++---- 2 files changed, 103 insertions(+), 46 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index c34a43139..d318153f6 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -36,6 +36,7 @@ from bpython import line as lineparts from bpython._py3compat import py3 + # Autocomplete modes SIMPLE = 'simple' SUBSTRING = 'substring' @@ -70,11 +71,13 @@ def matches(self, cursor_offset, line, **kwargs): an import or from statement, so it ought to return None. Completion types are used to: - * `locate(cur, line)` their initial target word to replace given a line and cursor + * `locate(cur, line)` their initial target word to replace given a + line and cursor * find `matches(cur, line)` that might replace that word * `format(match)` matches to be displayed to the user * determine whether suggestions should be `shown_before_tab` - * `substitute(cur, line, match)` in a match for what's found with `target` + * `substitute(cur, line, match)` in a match for what's found with + `target` """ raise NotImplementedError @@ -98,12 +101,14 @@ def shown_before_tab(self): once that has happened.""" return self._shown_before_tab + class CumulativeCompleter(BaseCompletionType): """Returns combined matches from several completers""" def __init__(self, completers): if not completers: - raise ValueError("CumulativeCompleter requires at least one completer") + raise ValueError( + "CumulativeCompleter requires at least one completer") self._completers = completers super(CumulativeCompleter, self).__init__(True) @@ -114,22 +119,18 @@ def locate(self, current_offset, line): def format(self, word): return self._completers[0].format(word) - def matches(self, cursor_offset, line, locals_, argspec, current_block, complete_magic_methods, history): + def matches(self, cursor_offset, line, **kwargs): all_matches = set() for completer in self._completers: # these have to be explicitely listed to deal with the different # signatures of various matches() methods of completers matches = completer.matches(cursor_offset=cursor_offset, line=line, - locals_=locals_, - argspec=argspec, - current_block=current_block, - complete_magic_methods=complete_magic_methods, - history=history) + **kwargs) if matches is not None: all_matches.update(matches) - return sorted(all_matches) + return all_matches class ImportCompletion(BaseCompletionType): @@ -143,6 +144,7 @@ def locate(self, current_offset, line): def format(self, word): return after_last_dot(word) + class FilenameCompletion(BaseCompletionType): def __init__(self): @@ -174,9 +176,14 @@ def format(self, filename): else: return filename + class AttrCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, locals_, **kwargs): + def matches(self, cursor_offset, line, **kwargs): + if 'locals_' not in kwargs: + return None + locals_ = kwargs['locals_'] + r = self.locate(cursor_offset, line) if r is None: return None @@ -195,8 +202,9 @@ def matches(self, cursor_offset, line, locals_, **kwargs): matches = set(''.join([text[:-i], m]) for m in attr_matches(methodtext, locals_)) - #TODO add open paren for methods via _callable_prefix (or decide not to) - # unless the first character is a _ filter out all attributes starting with a _ + # TODO add open paren for methods via _callable_prefix (or decide not + # to) unless the first character is a _ filter out all attributes + # starting with a _ if not text.split('.')[-1].startswith('_'): matches = set(match for match in matches if not match.split('.')[-1].startswith('_')) @@ -208,9 +216,14 @@ def locate(self, current_offset, line): def format(self, word): return after_last_dot(word) + class DictKeyCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, locals_, **kwargs): + def matches(self, cursor_offset, line, **kwargs): + if 'locals_' not in kwargs: + return None + locals_ = kwargs['locals_'] + r = self.locate(cursor_offset, line) if r is None: return None @@ -232,9 +245,14 @@ def locate(self, current_offset, line): def format(self, match): return match[:-1] + class MagicMethodCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, current_block, **kwargs): + def matches(self, cursor_offset, line, **kwargs): + if 'current_block' not in kwargs: + return None + current_block = kwargs['current_block'] + r = self.locate(cursor_offset, line) if r is None: return None @@ -246,13 +264,18 @@ def matches(self, cursor_offset, line, current_block, **kwargs): def locate(self, current_offset, line): return lineparts.current_method_definition_name(current_offset, line) + class GlobalCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, locals_, **kwargs): + def matches(self, cursor_offset, line, **kwargs): """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace that match. """ + if 'locals_' not in kwargs: + return None + locals_ = kwargs['locals_'] + r = self.locate(cursor_offset, line) if r is None: return None @@ -272,9 +295,14 @@ def matches(self, cursor_offset, line, locals_, **kwargs): def locate(self, current_offset, line): return lineparts.current_single_word(current_offset, line) + class ParameterNameCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, argspec, **kwargs): + def matches(self, cursor_offset, line, **kwargs): + if 'argspec' not in kwargs: + return None + argspec = kwargs['argspec'] + if not argspec: return None r = self.locate(cursor_offset, line) @@ -293,6 +321,7 @@ def matches(self, cursor_offset, line, argspec, **kwargs): def locate(self, current_offset, line): return lineparts.current_word(current_offset, line) + class StringLiteralAttrCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): @@ -309,6 +338,7 @@ def matches(self, cursor_offset, line, **kwargs): def locate(self, current_offset, line): return lineparts.current_string_literal_attr(current_offset, line) + try: import jedi except ImportError: @@ -317,14 +347,20 @@ def matches(self, cursor_offset, line, **kwargs): return None else: class JediCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, history, **kwargs): + def matches(self, cursor_offset, line, **kwargs): + if 'history' not in kwargs: + return None + history = kwargs['history'] + if not lineparts.current_word(cursor_offset, line): return None history = '\n'.join(history) + '\n' + line - script = jedi.Script(history, len(history.splitlines()), cursor_offset, 'fake.py') + script = jedi.Script(history, len(history.splitlines()), + cursor_offset, 'fake.py') completions = script.completions() if completions: - self._orig_start = cursor_offset - (len(completions[0].name) - len(completions[0].complete)) + diff = len(completions[0].name) - len(completions[0].complete) + self._orig_start = cursor_offset - diff else: self._orig_start = None return None @@ -333,22 +369,31 @@ def matches(self, cursor_offset, line, history, **kwargs): matches = [c.name for c in completions] if any(not m.lower().startswith(matches[0][0].lower()) for m in matches): - return None # Too general - giving completions starting with multiple letters + # Too general - giving completions starting with multiple + # letters + return None else: # case-sensitive matches only - return set([m for m in matches if m.startswith(first_letter)]) + return set(m for m in matches if m.startswith(first_letter)) def locate(self, cursor_offset, line): start = self._orig_start end = cursor_offset return start, end, line[start:end] - class MultilineJediCompletion(JediCompletion): - def matches(self, cursor_offset, line, current_block, history, **kwargs): + def matches(self, cursor_offset, line, **kwargs): + if 'current_block' not in kwargs or 'history' not in kwargs: + return None + current_block = kwargs['current_block'] + history = kwargs['history'] + if '\n' in current_block: - assert cursor_offset <= len(line), "%r %r" % (cursor_offset, line) - results = JediCompletion.matches(self, cursor_offset, line, history) + assert cursor_offset <= len(line), "%r %r" % (cursor_offset, + line) + results = super(MultilineJediCompletion, + self).matches(cursor_offset, line, + history=history) return results else: return None @@ -359,9 +404,9 @@ def get_completer(completers, cursor_offset, line, **kwargs): If no matches available, returns a tuple of an empty list and None - kwargs (all required): - cursor_offset is the current cursor column - line is a string of the current line + cursor_offset is the current cursor column + line is a string of the current line + kwargs (all optional): locals_ is a dictionary of the environment argspec is an inspect.ArgSpec instance for the current function where the cursor is @@ -372,11 +417,13 @@ def get_completer(completers, cursor_offset, line, **kwargs): """ for completer in completers: - matches = completer.matches(cursor_offset, line, **kwargs) + matches = completer.matches( + cursor_offset, line, **kwargs) if matches is not None: return sorted(matches), (completer if matches else None) return [], None + BPYTHON_COMPLETER = ( DictKeyCompletion(), StringLiteralAttrCompletion(), @@ -388,10 +435,10 @@ def get_completer(completers, cursor_offset, line, **kwargs): CumulativeCompleter((AttrCompletion(), ParameterNameCompletion())) ) -def get_completer_bpython(**kwargs): + +def get_completer_bpython(cursor_offset, line, **kwargs): """""" - return get_completer(BPYTHON_COMPLETER, - **kwargs) + return get_completer(BPYTHON_COMPLETER, cursor_offset, line, **kwargs) class EvaluationError(Exception): @@ -434,6 +481,7 @@ def attr_matches(text, namespace): matches = attr_lookup(obj, expr, attr) return matches + def attr_lookup(obj, expr, attr): """Second half of original attr_matches method factored out so it can be wrapped in a safe try/finally block in case anything bad happens to @@ -455,6 +503,7 @@ def attr_lookup(obj, expr, attr): matches.append("%s.%s" % (expr, word)) return matches + def _callable_postfix(value, word): """rlcompleter's _callable_postfix done right.""" with inspection.AttrCleaner(value): @@ -462,5 +511,6 @@ def _callable_postfix(value, word): word += '(' return word + def method_match(word, size, text): return word[:size] == text diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index caa16521d..847e65703 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -86,18 +86,18 @@ def test_no_completers_fails(self): def test_one_empty_completer_returns_empty(self): a = self.completer([]) cumulative = autocomplete.CumulativeCompleter([a]) - self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1, 1), []) + self.assertEqual(cumulative.matches(3, 'abc'), set()) def test_one_none_completer_returns_empty(self): a = self.completer(None) cumulative = autocomplete.CumulativeCompleter([a]) - self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1, 1), []) + self.assertEqual(cumulative.matches(3, 'abc'), set()) def test_two_completers_get_both(self): a = self.completer(['a']) b = self.completer(['b']) cumulative = autocomplete.CumulativeCompleter([a, b]) - self.assertEqual(cumulative.matches(3, 'abc', 1, 1, 1, 1, 1), (['a', 'b'])) + self.assertEqual(cumulative.matches(3, 'abc'), set(['a', 'b'])) class TestFilenameCompletion(unittest.TestCase): @@ -161,22 +161,23 @@ class TestDictKeyCompletion(unittest.TestCase): def test_set_of_keys_returned_when_matches_found(self): com = autocomplete.DictKeyCompletion() local = {'d': {"ab": 1, "cd": 2}} - self.assertSetEqual(com.matches(2, "d[", local), set(["'ab']", "'cd']"])) + self.assertSetEqual(com.matches(2, "d[", locals_=local), + set(["'ab']", "'cd']"])) def test_empty_set_returned_when_eval_error(self): com = autocomplete.DictKeyCompletion() local = {'e': {"ab": 1, "cd": 2}} - self.assertSetEqual(com.matches(2, "d[", local), set()) + self.assertSetEqual(com.matches(2, "d[", locals_=local), set()) def test_empty_set_returned_when_not_dict_type(self): com = autocomplete.DictKeyCompletion() local = {'l': ["ab", "cd"]} - self.assertSetEqual(com.matches(2, "l[", local),set()) + self.assertSetEqual(com.matches(2, "l[", locals_=local),set()) def test_obj_that_does_not_allow_conversion_to_bool(self): com = autocomplete.DictKeyCompletion() local = {'mNumPy': MockNumPy()} - self.assertSetEqual(com.matches(7, "mNumPy[", local), set()) + self.assertSetEqual(com.matches(7, "mNumPy[", locals_=local), set()) class Foo(object): a = 10 @@ -189,14 +190,16 @@ class TestAttrCompletion(unittest.TestCase): def test_att_matches_found_on_instance(self): com = autocomplete.AttrCompletion() - self.assertSetEqual(com.matches(2, 'a.', {'a': Foo()}), set(['a.method', 'a.a', 'a.b'])) + self.assertSetEqual(com.matches(2, 'a.', locals_={'a': Foo()}), + set(['a.method', 'a.a', 'a.b'])) class TestMagicMethodCompletion(unittest.TestCase): def test_magic_methods_complete_after_double_underscores(self): com = autocomplete.MagicMethodCompletion() block = "class Something(object)\n def __" - self.assertSetEqual(com.matches(10, ' def __', block), set(autocomplete.MAGIC_METHODS)) + self.assertSetEqual(com.matches(10, ' def __', current_block=block), + set(autocomplete.MAGIC_METHODS)) Comp = namedtuple('Completion', ['name', 'complete']) @@ -205,19 +208,23 @@ class TestMultilineJediCompletion(unittest.TestCase): @unittest.skipIf(not has_jedi, "jedi not available") def test_returns_none_with_single_line(self): com = autocomplete.MultilineJediCompletion() - self.assertEqual(com.matches(2, 'Va', 'Va', []), None) + self.assertEqual(com.matches(2, 'Va', current_block='Va', history=[]), + None) @unittest.skipIf(not has_jedi, "jedi not available") def test_returns_non_with_blank_second_line(self): com = autocomplete.MultilineJediCompletion() - self.assertEqual(com.matches(0, '', 'class Foo():\n', ['class Foo():']), None) + self.assertEqual(com.matches(0, '', current_block='class Foo():\n', + history=['class Foo():']), None) - def matches_from_completions(self, cursor, line, block, history, completions): + def matches_from_completions(self, cursor, line, block, history, + completions): with mock.patch('bpython.autocomplete.jedi.Script') as Script: script = Script.return_value script.completions.return_value = completions com = autocomplete.MultilineJediCompletion() - return com.matches(cursor, line, block, history) + return com.matches(cursor, line, current_block=block, + history=history) @unittest.skipIf(not has_jedi, "jedi not available") def test_completions_starting_with_different_letters(self): From 3ab27eb68acf7e8db1dc06c5524f70be9592ec38 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 29 Jan 2015 16:09:15 +0100 Subject: [PATCH 0308/1650] Lazy compilation of regex Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index d318153f6..efc51febb 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -455,12 +455,17 @@ def safe_eval(expr, namespace): raise EvaluationError -attr_matches_re = re.compile(r"(\w+(\.\w+)*)\.(\w*)") +attr_matches_re = None + def attr_matches(text, namespace): """Taken from rlcompleter.py and bent to my will. """ + global attr_matches_re + if attr_matches_re is None: + attr_matches_re = re.compile(r"(\w+(\.\w+)*)\.(\w*)") + # Gna, Py 2.6's rlcompleter searches for __call__ inside the # instance instead of the type, so we monkeypatch to prevent # side-effects (__getattr__/__getattribute__) From d25106473466f4839e6e35a1a44b7692fc35f117 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 29 Jan 2015 16:41:49 +0100 Subject: [PATCH 0309/1650] Also test latin1 Signed-off-by: Sebastian Ramacher --- bpython/test/fodder/encoding_ascii.py | 5 +++ bpython/test/fodder/encoding_latin1.py | 5 +++ bpython/test/fodder/encoding_utf8.py | 5 +++ bpython/test/test_inspection.py | 43 +++++++++++++++++++++++--- 4 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 bpython/test/fodder/encoding_ascii.py create mode 100644 bpython/test/fodder/encoding_latin1.py create mode 100644 bpython/test/fodder/encoding_utf8.py diff --git a/bpython/test/fodder/encoding_ascii.py b/bpython/test/fodder/encoding_ascii.py new file mode 100644 index 000000000..844c2becd --- /dev/null +++ b/bpython/test/fodder/encoding_ascii.py @@ -0,0 +1,5 @@ +# -*- coding: ascii -*- + +def foo(): + """Test""" + pass diff --git a/bpython/test/fodder/encoding_latin1.py b/bpython/test/fodder/encoding_latin1.py new file mode 100644 index 000000000..2f4fd15b5 --- /dev/null +++ b/bpython/test/fodder/encoding_latin1.py @@ -0,0 +1,5 @@ +# -*- coding: latin1 -*- + +def foo(): + """Test """ + pass diff --git a/bpython/test/fodder/encoding_utf8.py b/bpython/test/fodder/encoding_utf8.py new file mode 100644 index 000000000..933dc4afe --- /dev/null +++ b/bpython/test/fodder/encoding_utf8.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +def foo(): + """Test äöü""" + pass diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 0e428cc85..10c0863f3 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -1,9 +1,25 @@ +# -*- coding: utf-8 -*- + try: import unittest2 as unittest except ImportError: import unittest from bpython import inspection +from bpython.test.fodder import encoding_ascii +from bpython.test.fodder import encoding_latin1 +from bpython.test.fodder import encoding_utf8 + +foo_ascii_only = u'''def foo(): + """Test""" + pass +''' + +foo_non_ascii = u'''def foo(): + """Test äöü""" + pass +''' + class TestInspection(unittest.TestCase): def test_is_callable(self): @@ -61,11 +77,30 @@ def spam(eggs=23, foobar="yay"): self.assertEqual(repr(defaults[0]), "23") self.assertEqual(repr(defaults[1]), "'yay'") - def test_get_encoding(self): - self.assertEqual(inspection.get_encoding(inspection), 'ascii') - from bpython.test import test_curtsies_painting - self.assertEqual(inspection.get_encoding(test_curtsies_painting), 'utf8') + def test_get_encoding_ascii(self): + self.assertEqual(inspection.get_encoding(encoding_ascii), 'ascii') + self.assertEqual(inspection.get_encoding(encoding_ascii.foo), 'ascii') + + def test_get_encoding_latin1(self): + self.assertEqual(inspection.get_encoding(encoding_latin1), 'latin1') + self.assertEqual(inspection.get_encoding(encoding_latin1.foo), + 'latin1') + + def test_get_encoding_utf8(self): + self.assertEqual(inspection.get_encoding(encoding_utf8), 'utf-8') + self.assertEqual(inspection.get_encoding(encoding_utf8.foo), 'utf-8') + + def test_get_source_ascii(self): + self.assertEqual(inspection.get_source_unicode(encoding_ascii.foo), + foo_ascii_only) + + def test_get_source_utf8(self): + self.assertEqual(inspection.get_source_unicode(encoding_utf8.foo), + foo_non_ascii) + def test_get_source_latin1(self): + self.assertEqual(inspection.get_source_unicode(encoding_latin1.foo), + foo_non_ascii) if __name__ == '__main__': unittest.main() From a5e541bf092a9a12a7cbbd5291b3b87a010c3a4f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 29 Jan 2015 21:10:07 +0100 Subject: [PATCH 0310/1650] Detect if Unicode box characters are supported in user's locale (fixes 295) Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 5 +---- bpython/config.py | 19 +++++++++++++++++-- bpython/inspection.py | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index b50449d89..50be05b8d 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -72,7 +72,7 @@ from bpython import importcompletion # This for config -from bpython.config import Struct +from bpython.config import Struct, getpreferredencoding # This for keys from bpython.keys import cli_key_dispatch as key_dispatch @@ -98,9 +98,6 @@ # --- -def getpreferredencoding(): - return locale.getpreferredencoding() or sys.getdefaultencoding() - def calculate_screen_lines(tokens, width, cursor=0): """Given a stream of tokens and a screen width plus an optional initial cursor position, return the amount of needed lines on the diff --git a/bpython/config.py b/bpython/config.py index e247f0524..455847283 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -3,11 +3,11 @@ from __future__ import with_statement import os import sys +import locale from ConfigParser import ConfigParser from itertools import chain from bpython.keys import cli_key_dispatch as key_dispatch from bpython.autocomplete import SIMPLE as default_completion - import bpython.autocomplete @@ -16,6 +16,21 @@ class Struct(object): to and use for various arbitrary things.""" +def getpreferredencoding(): + """Get the user's preferred encoding.""" + return locale.getpreferredencoding() or sys.getdefaultencoding() + + +def supports_box_chars(): + """Check if the encoding suppors Unicode box characters.""" + try: + for c in (u'│', u'│', u'─', u'─', u'└', u'┘', u'┌', u'┐'): + c.encode(getpreferredencoding()) + return True + except UnicodeEncodeError: + return False + + def get_config_home(): """Returns the base directory for bpython's configuration files.""" xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config') @@ -256,7 +271,7 @@ def get_key_no_doublebind(command): struct.autocomplete_mode = default_completion # set box drawing characters - if config.getboolean('general', 'unicode_box'): + if config.getboolean('general', 'unicode_box') and supports_box_chars(): struct.left_border = u'│' struct.right_border = u'│' struct.top_border = u'─' diff --git a/bpython/inspection.py b/bpython/inspection.py index 02be5a415..0b0863bc6 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -1,6 +1,6 @@ # The MIT License # -# Copyright (c) 2009-2011 the bpython authors. +# Copyright (c) 2009-2015 the bpython authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal From 8ff8678a6b832c8c4459b5dd4dd46be94900a8b7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 30 Jan 2015 01:41:03 +0100 Subject: [PATCH 0311/1650] Catch jedi errors Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index efc51febb..65f0d5fbf 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -347,6 +347,7 @@ def matches(self, cursor_offset, line, **kwargs): return None else: class JediCompletion(BaseCompletionType): + def matches(self, cursor_offset, line, **kwargs): if 'history' not in kwargs: return None @@ -355,9 +356,13 @@ def matches(self, cursor_offset, line, **kwargs): if not lineparts.current_word(cursor_offset, line): return None history = '\n'.join(history) + '\n' + line - script = jedi.Script(history, len(history.splitlines()), - cursor_offset, 'fake.py') - completions = script.completions() + try: + script = jedi.Script(history, len(history.splitlines()), + cursor_offset, 'fake.py') + completions = script.completions() + except jedi.NotFoundError: + self._orig_start = None + return None if completions: diff = len(completions[0].name) - len(completions[0].complete) self._orig_start = cursor_offset - diff @@ -381,6 +386,7 @@ def locate(self, cursor_offset, line): end = cursor_offset return start, end, line[start:end] + class MultilineJediCompletion(JediCompletion): def matches(self, cursor_offset, line, **kwargs): if 'current_block' not in kwargs or 'history' not in kwargs: From e5cefc5c3715c81c60c6a31895743fe33a242bcf Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 30 Jan 2015 22:23:34 -0500 Subject: [PATCH 0312/1650] fix #469 and #471 --- bpython/curtsiesfrontend/repl.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 48ed3fd4d..bbc318df2 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1299,13 +1299,12 @@ def take_back_buffer_line(self): self.cursor_offset = len(self.current_line) self.display_buffer.pop() self.buffer.pop() + self.history.pop() def prompt_undo(self): if self.buffer: return self.take_back_buffer_line() - self.reevaluate() - def prompt_for_undo(): n = BpythonRepl.prompt_undo(self) if n > 0: @@ -1334,7 +1333,7 @@ def reevaluate(self, insert_into_history=False): self.reevaluating = True sys.stdin = ReevaluateFakeStdin(self.stdin, self) for line in old_logical_lines: - self.current_line = line + self._current_line = line self.on_enter(insert_into_history=insert_into_history) while self.fake_refresh_requested: self.fake_refresh_requested = False @@ -1352,7 +1351,7 @@ def reevaluate(self, insert_into_history=False): self.inconsistent_history = True logger.debug('after rewind, self.inconsistent_history is %r', self.inconsistent_history) - self.cursor_offset = 0 + self._cursor_offset = 0 self.current_line = '' def getstdout(self): From 3db074b0ad8a81c49cf63b173dff13089579cdd1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 30 Jan 2015 02:34:13 +0100 Subject: [PATCH 0313/1650] Lazy compile all regex Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 7 +---- bpython/line.py | 61 ++++++++++++++++++++++++++++++----------- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 65f0d5fbf..7ab5eae59 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -26,7 +26,6 @@ import abc import keyword import os -import re import rlcompleter from glob import glob @@ -461,17 +460,13 @@ def safe_eval(expr, namespace): raise EvaluationError -attr_matches_re = None +attr_matches_re = lineparts.LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") def attr_matches(text, namespace): """Taken from rlcompleter.py and bent to my will. """ - global attr_matches_re - if attr_matches_re is None: - attr_matches_re = re.compile(r"(\w+(\.\w+)*)\.(\w*)") - # Gna, Py 2.6's rlcompleter searches for __call__ inside the # instance instead of the type, so we monkeypatch to prevent # side-effects (__getattr__/__getattribute__) diff --git a/bpython/line.py b/bpython/line.py index 883c6fc96..45a79563c 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -7,7 +7,34 @@ from itertools import chain -current_word_re = re.compile(r'[\w_][\w0-9._]*[(]?') +class LazyReCompile(object): + + def __init__(self, regex): + self.regex = regex + self.compiled = None + + def compile_regex(method): + def _impl(self, *args, **kwargs): + if self.compiled is None: + self.compiled = re.compile(self.regex) + return method(self, *args, **kwargs) + return _impl + + @compile_regex + def finditer(self, *args, **kwargs): + return self.compiled.finditer(*args, **kwargs) + + @compile_regex + def search(self, *args, **kwargs): + return self.compiled.search(*args, **kwargs) + + @compile_regex + def match(self, *args, **kwargs): + return self.compiled.match(*args, **kwargs) + + +current_word_re = LazyReCompile(r'[\w_][\w0-9._]*[(]?') + def current_word(cursor_offset, line): """the object.attribute.attribute just before or under the cursor""" @@ -26,7 +53,7 @@ def current_word(cursor_offset, line): return (start, end, word) -current_dict_key_re = re.compile(r'''[\w_][\w0-9._]*\[([\w0-9._(), '"]*)''') +current_dict_key_re = LazyReCompile(r'''[\w_][\w0-9._]*\[([\w0-9._(), '"]*)''') def current_dict_key(cursor_offset, line): """If in dictionary completion, return the current key""" @@ -37,7 +64,8 @@ def current_dict_key(cursor_offset, line): return None -current_dict_re = re.compile(r'''([\w_][\w0-9._]*)\[([\w0-9._(), '"]*)''') +current_dict_re = LazyReCompile(r'''([\w_][\w0-9._]*)\[([\w0-9._(), '"]*)''') + def current_dict(cursor_offset, line): """If in dictionary completion, return the dict that should be used""" @@ -48,9 +76,10 @@ def current_dict(cursor_offset, line): return None -current_string_re = re.compile( +current_string_re = LazyReCompile( '''(?P(?:""")|"|(?:''\')|')(?:((?P.+?)(?P=open))|(?P.+))''') + def current_string(cursor_offset, line): """If inside a string of nonzero length, return the string (excluding quotes) @@ -63,7 +92,7 @@ def current_string(cursor_offset, line): return None -current_object_re = re.compile(r'([\w_][\w0-9_]*)[.]') +current_object_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]') def current_object(cursor_offset, line): """If in attribute completion, the object on which attribute should be looked up""" @@ -82,7 +111,7 @@ def current_object(cursor_offset, line): return start, start+len(s), s -current_object_attribute_re = re.compile(r'([\w_][\w0-9_]*)[.]?') +current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]?') def current_object_attribute(cursor_offset, line): """If in attribute completion, the attribute being completed""" @@ -97,7 +126,7 @@ def current_object_attribute(cursor_offset, line): return None -current_from_import_from_re = re.compile(r'from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*') +current_from_import_from_re = LazyReCompile(r'from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*') def current_from_import_from(cursor_offset, line): """If in from import completion, the word after from @@ -117,9 +146,9 @@ def current_from_import_from(cursor_offset, line): return None -current_from_import_import_re_1 = re.compile(r'from\s([\w0-9_.]*)\s+import') -current_from_import_import_re_2 = re.compile(r'([\w0-9_]+)') -current_from_import_import_re_3 = re.compile(r'[,][ ]([\w0-9_]*)') +current_from_import_import_re_1 = LazyReCompile(r'from\s([\w0-9_.]*)\s+import') +current_from_import_import_re_2 = LazyReCompile(r'([\w0-9_]+)') +current_from_import_import_re_3 = LazyReCompile(r'[,][ ]([\w0-9_]*)') def current_from_import_import(cursor_offset, line): """If in from import completion, the word after import being completed @@ -141,9 +170,9 @@ def current_from_import_import(cursor_offset, line): return None -current_import_re_1 = re.compile(r'import') -current_import_re_2 = re.compile(r'([\w0-9_.]+)') -current_import_re_3 = re.compile(r'[,][ ]([\w0-9_.]*)') +current_import_re_1 = LazyReCompile(r'import') +current_import_re_2 = LazyReCompile(r'([\w0-9_.]+)') +current_import_re_3 = LazyReCompile(r'[,][ ]([\w0-9_.]*)') def current_import(cursor_offset, line): #TODO allow for multiple as's @@ -161,7 +190,7 @@ def current_import(cursor_offset, line): return start, end, m.group(1) -current_method_definition_name_re = re.compile("def\s+([a-zA-Z_][\w]*)") +current_method_definition_name_re = LazyReCompile("def\s+([a-zA-Z_][\w]*)") def current_method_definition_name(cursor_offset, line): """The name of a method being defined""" @@ -172,7 +201,7 @@ def current_method_definition_name(cursor_offset, line): return None -current_single_word_re = re.compile(r"(? Date: Sun, 1 Feb 2015 00:23:19 +0100 Subject: [PATCH 0314/1650] EOL 80, pep8 and less parens Signed-off-by: Sebastian Ramacher --- bpython/line.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 45a79563c..913364e47 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -1,7 +1,8 @@ """Extracting and changing portions of the current line -All functions take cursor offset from the beginning of the line and the line of python code, -and return None, or a tuple of the start index, end index, and the word""" +All functions take cursor offset from the beginning of the line and the line of +Python code, and return None, or a tuple of the start index, end index, and the +word.""" import re from itertools import chain @@ -55,6 +56,7 @@ def current_word(cursor_offset, line): current_dict_key_re = LazyReCompile(r'''[\w_][\w0-9._]*\[([\w0-9._(), '"]*)''') + def current_dict_key(cursor_offset, line): """If in dictionary completion, return the current key""" matches = current_dict_key_re.finditer(line) @@ -94,6 +96,7 @@ def current_string(cursor_offset, line): current_object_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]') + def current_object(cursor_offset, line): """If in attribute completion, the object on which attribute should be looked up""" match = current_word(cursor_offset, line) @@ -113,6 +116,7 @@ def current_object(cursor_offset, line): current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]?') + def current_object_attribute(cursor_offset, line): """If in attribute completion, the attribute being completed""" match = current_word(cursor_offset, line) @@ -128,6 +132,7 @@ def current_object_attribute(cursor_offset, line): current_from_import_from_re = LazyReCompile(r'from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*') + def current_from_import_from(cursor_offset, line): """If in from import completion, the word after from @@ -150,6 +155,7 @@ def current_from_import_from(cursor_offset, line): current_from_import_import_re_2 = LazyReCompile(r'([\w0-9_]+)') current_from_import_import_re_3 = LazyReCompile(r'[,][ ]([\w0-9_]*)') + def current_from_import_import(cursor_offset, line): """If in from import completion, the word after import being completed @@ -174,6 +180,7 @@ def current_from_import_import(cursor_offset, line): current_import_re_2 = LazyReCompile(r'([\w0-9_.]+)') current_import_re_3 = LazyReCompile(r'[,][ ]([\w0-9_.]*)') + def current_import(cursor_offset, line): #TODO allow for multiple as's baseline = current_import_re_1.search(line) @@ -192,6 +199,7 @@ def current_import(cursor_offset, line): current_method_definition_name_re = LazyReCompile("def\s+([a-zA-Z_][\w]*)") + def current_method_definition_name(cursor_offset, line): """The name of a method being defined""" matches = current_method_definition_name_re.finditer(line) @@ -203,11 +211,12 @@ def current_method_definition_name(cursor_offset, line): current_single_word_re = LazyReCompile(r"(?= cursor_offset): + if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: return m.start(1), m.end(1), m.group(1) return None @@ -215,7 +224,8 @@ def current_single_word(cursor_offset, line): def current_dotted_attribute(cursor_offset, line): """The dotted attribute-object pair before the cursor""" match = current_word(cursor_offset, line) - if match is None: return None + if match is None: + return None start, end, word = match if '.' in word[1:]: return start, end, word @@ -225,10 +235,11 @@ def current_dotted_attribute(cursor_offset, line): "('''" + r'''|"""|'|")((?:(?=([^"'\\]+|\\.|(?!\1)["']))\3)*)\1[.]([a-zA-Z_]?[\w]*)''') + def current_string_literal_attr(cursor_offset, line): """The attribute following a string literal""" matches = current_string_literal_attr_re.finditer(line) for m in matches: - if (m.start(4) <= cursor_offset and m.end(4) >= cursor_offset): + if m.start(4) <= cursor_offset and m.end(4) >= cursor_offset: return m.start(4), m.end(4), m.group(4) return None From 059d32ca4fdfd5585d61248dc8a5f4d56bb47304 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 1 Feb 2015 00:23:44 +0100 Subject: [PATCH 0315/1650] Mention the cffi issue (fixes #470) Signed-off-by: Sebastian Ramacher --- README.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.rst b/README.rst index 05230ed84..c3708951f 100644 --- a/README.rst +++ b/README.rst @@ -17,6 +17,17 @@ Dependencies * babel (optional, for internationalization) * watchdog (optional, for monitoring imported modules for changes) +If you are using Python 2 on Mac OS X, the following dependencies are required +as well: + +* pyOpenSSL +* ndg-httpsclien +* pyasn1 + +If you have problems installing cffi which is needed by pyOpenSSL, +please take a look at https://cffi.readthedocs.org/en/release-0.8/#macos-x. + + bpython-urwid ------------- ``bpython-urwid`` requires the following additional packages: From a7d75d4fe52b45b1a780aadca54db1f29411fab3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 1 Feb 2015 22:41:27 +0100 Subject: [PATCH 0316/1650] Update AUTHORS Signed-off-by: Sebastian Ramacher --- AUTHORS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AUTHORS b/AUTHORS index 956a2e284..c873f79a8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,13 +11,17 @@ Other contributors are (in alphabetical order): * Eike Hein * Allison Kaptur * Jason Laster +* Miriam Lauter +* Mary Mokuolu * Brandon Navra * Michele Orrù * Pavel Panchekha +* Keyan Pishdadian * Sebastian Ramacher * Amjith Ramanujam * Andreas Stührk * Simon de Vlieger +* Tarek Ziade * Marien Zwart Many thanks for all contributions! From 5540c1d4b1438c23a3596259b61b26a6a273d5e8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 1 Feb 2015 22:51:01 +0100 Subject: [PATCH 0317/1650] Throw exception instead of assert Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 78220d796..5000e8a1c 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -245,12 +245,14 @@ def update(self, cursor_offset, current_line, matches, completer): """Called to reset the match index and update the word being replaced Should only be called if there's a target to update - otherwise, call clear""" + + if matches is None: + raise ValueError("Matches may not be None.") + self.orig_cursor_offset = cursor_offset self.orig_line = current_line - assert matches is not None self.matches = matches self.completer = completer - #assert self.completer.locate(self.orig_cursor_offset, self.orig_line) is not None, (self.completer.locate, self.orig_cursor_offset, self.orig_line) self.index = -1 self.start, self.end, self.current_word = self.completer.locate(self.orig_cursor_offset, self.orig_line) From e5e6bd8258477c39f1fb1431523b1b4ec8991d9f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 1 Feb 2015 23:04:56 +0100 Subject: [PATCH 0318/1650] More translatable strings Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 37 ++-- bpython/translations/bpython.pot | 126 +++++++++---- .../translations/de/LC_MESSAGES/bpython.po | 153 +++++++++++----- .../translations/es_ES/LC_MESSAGES/bpython.po | 126 +++++++++---- .../translations/fr_FR/LC_MESSAGES/bpython.po | 167 +++++++++++++----- .../translations/it_IT/LC_MESSAGES/bpython.po | 126 +++++++++---- .../translations/nl_NL/LC_MESSAGES/bpython.po | 126 +++++++++---- 7 files changed, 618 insertions(+), 243 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 5000e8a1c..bbf4b85a5 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -649,12 +649,12 @@ def write2file(self): buffer to disk.""" try: - fn = self.interact.file_prompt('Save to file (Esc to cancel): ') + fn = self.interact.file_prompt(_('Save to file (Esc to cancel): ')) if not fn: - self.interact.notify("Save cancelled.") + self.interact.notify(_('Save cancelled.')) return except ValueError: - self.interact.notify("Save cancelled.") + self.interact.notify(_('Save cancelled.')) return if fn.startswith('~'): @@ -664,15 +664,17 @@ def write2file(self): mode = 'w' if os.path.exists(fn): - mode = self.interact.file_prompt('%s already exists. Do you want ' - 'to (c)ancel, (o)verwrite or ' - '(a)ppend? ' % (fn, )) - if mode in ('o', 'overwrite'): + mode = self.interact.file_prompt(_('%s already exists. Do you ' + 'want to (c)ancel, ' + 'wantch to (c)ancel, ' + ' (o)verwrite or ' + '(a)ppend? ') % (fn, )) + if mode in ('o', 'overwrite', _('overwrite')): mode = 'w' - elif mode in ('a', 'append'): + elif mode in ('a', 'append', _('append')): mode = 'a' else: - self.interact.notify('Save cancelled.') + self.interact.notify(_('Save cancelled.')) return s = self.formatforfile(self.getstdout()) @@ -680,10 +682,11 @@ def write2file(self): try: with open(fn, mode) as f: f.write(s) - except IOError: - self.interact.notify("Disk write error for file '%s'." % (fn, )) + except IOError as e: + self.interact.notify(_("Error writing file '%s': %s") % (fn, + str(e))) else: - self.interact.notify('Saved to %s.' % (fn, )) + self.interact.notify(_('Saved to %s.') % (fn, )) def copy2clipboard(self): """Copy current content to clipboard.""" @@ -708,7 +711,7 @@ def pastebin(self, s=None): if (self.config.pastebin_confirm and not self.interact.confirm(_("Pastebin buffer? (y/N) "))): - self.interact.notify(_("Pastebin aborted")) + self.interact.notify(_("Pastebin aborted.")) return return self.do_pastebin(s) @@ -1023,15 +1026,17 @@ def edit_config(self): with open(self.config.config_path, 'w') as f: f.write(default_config) except (IOError, OSError) as e: - self.interact.notify('error creating file: %r' % e) + self.interact.notify(_("Error writing file '%s': %s") % \ + (self.config.config.path, str(e))) return False else: return False if self.open_in_external_editor(self.config.config_path): - self.interact.notify('bpython config file edited. Restart bpython for changes to take effect.') + self.interact.notify(_('bpython config file edited. Restart ' + 'bpython for changes to take effect.')) else: - self.interact.notify('error editing config file') + self.interact.notify(_('Error editing config file.')) def next_indentation(line, tab_length): diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 0685dc0ae..8325bed18 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.13-442\n" +"Project-Id-Version: bpython 0.13-492\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-01-23 23:19+0100\n" +"POT-Creation-Date: 2015-02-01 23:02+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -40,31 +40,31 @@ msgstr "" msgid "Print version and exit." msgstr "" -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "y" msgstr "" -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "yes" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1696 msgid "Save" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1700 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "" @@ -76,99 +76,157 @@ msgstr "" msgid "enter lines of file as though interactively typed" msgstr "" -#: bpython/history.py:236 +#: bpython/history.py:216 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:477 +#: bpython/repl.py:513 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:482 +#: bpython/repl.py:518 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:487 +#: bpython/repl.py:523 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:489 +#: bpython/repl.py:525 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:655 +#: bpython/repl.py:652 +msgid "Save to file (Esc to cancel): " +msgstr "" + +#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +msgid "Save cancelled." +msgstr "" + +#: bpython/repl.py:667 +#, python-format +msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " +msgstr "" + +#: bpython/repl.py:671 +msgid "overwrite" +msgstr "" + +#: bpython/repl.py:673 +msgid "append" +msgstr "" + +#: bpython/repl.py:685 bpython/repl.py:1028 +#, python-format +msgid "Error writing file '%s': %s" +msgstr "" + +#: bpython/repl.py:688 +#, python-format +msgid "Saved to %s." +msgstr "" + +#: bpython/repl.py:694 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:701 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:664 +#: bpython/repl.py:703 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:673 +#: bpython/repl.py:712 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:674 -msgid "Pastebin aborted" +#: bpython/repl.py:713 +msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:681 +#: bpython/repl.py:720 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:700 bpython/repl.py:728 +#: bpython/repl.py:739 bpython/repl.py:767 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:705 +#: bpython/repl.py:744 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:721 +#: bpython/repl.py:760 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:740 +#: bpython/repl.py:779 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:743 +#: bpython/repl.py:782 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:750 +#: bpython/repl.py:789 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:754 +#: bpython/repl.py:793 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:761 +#: bpython/repl.py:800 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:767 +#: bpython/repl.py:806 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:953 +#: bpython/repl.py:839 +#, python-format +msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" +msgstr "" + +#: bpython/repl.py:846 bpython/repl.py:850 +msgid "Undo canceled" +msgstr "" + +#: bpython/repl.py:853 +#, python-format +msgid "Undoing 1 line... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:856 +#, python-format +msgid "Undoing %d lines... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:1018 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" +#: bpython/repl.py:1035 +msgid "bpython config file edited. Restart bpython for changes to take effect." +msgstr "" + +#: bpython/repl.py:1038 +msgid "Error editing config file." +msgstr "" + #: bpython/urwid.py:617 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " @@ -196,16 +254,16 @@ msgstr "" msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:470 +#: bpython/curtsiesfrontend/repl.py:472 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 9b64ef25c..45f2e5b4d 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,21 +7,23 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-01-23 23:19+0100\n" -"PO-Revision-Date: 2015-01-23 23:19+0100\n" +"POT-Creation-Date: 2015-02-01 23:02+0100\n" +"PO-Revision-Date: 2015-02-01 23:02+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: de \n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" +"Language: de\n" +"X-Generator: Poedit 1.6.10\n" #: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back " -"to the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back to " +"the regular Python interpreter." msgstr "" #: bpython/args.py:67 @@ -40,31 +42,31 @@ msgstr "Gib Ausgabe beim Beenden nicht ernaut auf stdout aus." msgid "Print version and exit." msgstr "Zeige Versionsinformationen an und beende." -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "y" msgstr "j" -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "yes" msgstr "ja" -#: bpython/cli.py:1696 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "Rückgängig" -#: bpython/cli.py:1697 +#: bpython/cli.py:1696 msgid "Save" msgstr "Speichern" -#: bpython/cli.py:1698 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1700 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "Quellcode anzeigen" @@ -76,102 +78,167 @@ msgstr "" msgid "enter lines of file as though interactively typed" msgstr "" -#: bpython/history.py:236 +#: bpython/history.py:216 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" -#: bpython/repl.py:477 +#: bpython/repl.py:513 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:482 +#: bpython/repl.py:518 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" -#: bpython/repl.py:487 +#: bpython/repl.py:523 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/repl.py:489 +#: bpython/repl.py:525 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" -#: bpython/repl.py:655 +#: bpython/repl.py:652 +msgid "Save to file (Esc to cancel): " +msgstr "In Datei speichern (Esc um abzubrechen): " + +#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +msgid "Save cancelled." +msgstr "Speichern abgebrochen." + +#: bpython/repl.py:667 +#, python-format +msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " +msgstr "" +"%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" + +#: bpython/repl.py:671 +msgid "overwrite" +msgstr "überschreiben" + +#: bpython/repl.py:673 +msgid "append" +msgstr "anhängen" + +#: bpython/repl.py:685 bpython/repl.py:1028 +#, python-format +msgid "Error writing file '%s': %s" +msgstr "Fehler beim Schreiben in Datei '%s': %s" + +#: bpython/repl.py:688 +#, python-format +msgid "Saved to %s." +msgstr "Nach %s gespeichert." + +#: bpython/repl.py:694 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/repl.py:662 +#: bpython/repl.py:701 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/repl.py:664 +#: bpython/repl.py:703 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:673 +#: bpython/repl.py:712 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:674 -msgid "Pastebin aborted" +#: bpython/repl.py:713 +msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:681 +#: bpython/repl.py:720 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:700 bpython/repl.py:728 +#: bpython/repl.py:739 bpython/repl.py:767 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." -#: bpython/repl.py:705 +#: bpython/repl.py:744 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:721 +#: bpython/repl.py:760 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:740 +#: bpython/repl.py:779 msgid "Upload failed: Helper program not found." -msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht gefunden werden." +msgstr "" +"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht gefunden werden." -#: bpython/repl.py:743 +#: bpython/repl.py:782 msgid "Upload failed: Helper program could not be run." msgstr "" -"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht ausgeführt " -"werden." +"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht ausgeführt werden." -#: bpython/repl.py:750 +#: bpython/repl.py:789 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm beendete mit Status %s." -#: bpython/repl.py:754 +#: bpython/repl.py:793 msgid "Upload failed: No output from helper program." -msgstr "Hochladen ist fehlgeschlagen: Keine Ausgabe von Hilfsprogramm vorhanden." +msgstr "" +"Hochladen ist fehlgeschlagen: Keine Ausgabe von Hilfsprogramm vorhanden." -#: bpython/repl.py:761 -msgid "Upload failed: Failed to recognize the helper program's output as an URL." +#: bpython/repl.py:800 +msgid "" +"Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" "Hochladen ist fehlgeschlagen: Konte Ausgabe von Hilfsprogramm nicht " "verarbeiten." -#: bpython/repl.py:767 +#: bpython/repl.py:806 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:953 +#: bpython/repl.py:839 +#, python-format +msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" +msgstr "" + +#: bpython/repl.py:846 bpython/repl.py:850 +msgid "Undo canceled" +msgstr "Rückgängigmachen abgebrochen" + +#: bpython/repl.py:853 +#, python-format +msgid "Undoing 1 line... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:856 +#, python-format +msgid "Undoing %d lines... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:1018 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" +"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt werden? " +"(j/N)" + +#: bpython/repl.py:1035 +msgid "bpython config file edited. Restart bpython for changes to take effect." +msgstr "" +"bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " +"Änderungen übernommen werden." + +#: bpython/repl.py:1038 +msgid "Error editing config file." +msgstr "Fehler beim Bearbeiten der Konfigurationsdatei." #: bpython/urwid.py:617 #, python-format @@ -200,16 +267,16 @@ msgstr "" msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#: bpython/curtsiesfrontend/repl.py:470 +#: bpython/curtsiesfrontend/repl.py:472 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" -msgstr "" +msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 949666af2..cec821d93 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-01-23 23:19+0100\n" -"PO-Revision-Date: 2015-01-23 23:20+0100\n" +"POT-Creation-Date: 2015-02-01 23:02+0100\n" +"PO-Revision-Date: 2015-02-01 23:04+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -40,31 +40,31 @@ msgstr "" msgid "Print version and exit." msgstr "" -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "y" msgstr "s" -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "yes" msgstr "si" -#: bpython/cli.py:1696 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1696 msgid "Save" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1700 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "" @@ -76,99 +76,157 @@ msgstr "" msgid "enter lines of file as though interactively typed" msgstr "" -#: bpython/history.py:236 +#: bpython/history.py:216 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:477 +#: bpython/repl.py:513 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:482 +#: bpython/repl.py:518 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:487 +#: bpython/repl.py:523 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:489 +#: bpython/repl.py:525 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:655 +#: bpython/repl.py:652 +msgid "Save to file (Esc to cancel): " +msgstr "" + +#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +msgid "Save cancelled." +msgstr "" + +#: bpython/repl.py:667 +#, python-format +msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " +msgstr "" + +#: bpython/repl.py:671 +msgid "overwrite" +msgstr "" + +#: bpython/repl.py:673 +msgid "append" +msgstr "" + +#: bpython/repl.py:685 bpython/repl.py:1028 +#, python-format +msgid "Error writing file '%s': %s" +msgstr "" + +#: bpython/repl.py:688 +#, python-format +msgid "Saved to %s." +msgstr "" + +#: bpython/repl.py:694 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:701 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:664 +#: bpython/repl.py:703 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:673 +#: bpython/repl.py:712 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:674 -msgid "Pastebin aborted" +#: bpython/repl.py:713 +msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:681 +#: bpython/repl.py:720 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:700 bpython/repl.py:728 +#: bpython/repl.py:739 bpython/repl.py:767 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:705 +#: bpython/repl.py:744 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:721 +#: bpython/repl.py:760 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:740 +#: bpython/repl.py:779 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:743 +#: bpython/repl.py:782 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:750 +#: bpython/repl.py:789 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:754 +#: bpython/repl.py:793 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:761 +#: bpython/repl.py:800 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:767 +#: bpython/repl.py:806 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:953 +#: bpython/repl.py:839 +#, python-format +msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" +msgstr "" + +#: bpython/repl.py:846 bpython/repl.py:850 +msgid "Undo canceled" +msgstr "" + +#: bpython/repl.py:853 +#, python-format +msgid "Undoing 1 line... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:856 +#, python-format +msgid "Undoing %d lines... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:1018 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" +#: bpython/repl.py:1035 +msgid "bpython config file edited. Restart bpython for changes to take effect." +msgstr "" + +#: bpython/repl.py:1038 +msgid "Error editing config file." +msgstr "" + #: bpython/urwid.py:617 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " @@ -198,16 +256,16 @@ msgstr "" msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:470 +#: bpython/curtsiesfrontend/repl.py:472 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index 69a7f127f..16e63ab60 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,24 +6,27 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-01-23 23:19+0100\n" -"Last-Translator: Tarek Ziadé \n" +"POT-Creation-Date: 2015-02-01 23:02+0100\n" +"PO-Revision-Date: 2015-02-01 23:03+0100\n" +"Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" +"Language: fr_FR\n" +"X-Generator: Poedit 1.6.10\n" #: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back " -"to the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back to " +"the regular Python interpreter." msgstr "" "Utilisation: %prog [options] [fichier [arguments]]\n" -"NOTE: Si bpython ne reconnaît pas un des arguments fournis, " -"l'interpréteur Python classique sera lancé" +"NOTE: Si bpython ne reconnaît pas un des arguments fournis, l'interpréteur " +"Python classique sera lancé" #: bpython/args.py:67 msgid "Use CONFIG instead of default config file." @@ -31,7 +34,8 @@ msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." #: bpython/args.py:69 msgid "Drop to bpython shell after running file instead of exiting." -msgstr "Aller dans le shell bpython après l'exécution du fichier au lieu de quitter." +msgstr "" +"Aller dans le shell bpython après l'exécution du fichier au lieu de quitter." #: bpython/args.py:72 msgid "Don't flush the output to stdout." @@ -41,31 +45,31 @@ msgstr "Ne pas purger la sortie vers stdout." msgid "Print version and exit." msgstr "Afficher la version et quitter." -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "y" msgstr "o" -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "yes" msgstr "oui" -#: bpython/cli.py:1696 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "Rembobiner" -#: bpython/cli.py:1697 +#: bpython/cli.py:1696 msgid "Save" msgstr "Sauvegarder" -#: bpython/cli.py:1698 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1700 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "Montrer le code source" @@ -77,103 +81,169 @@ msgstr "logger les messages de debug dans bpython.log" msgid "enter lines of file as though interactively typed" msgstr "saisir les lignes du fichier interactivement" -#: bpython/history.py:236 +#: bpython/history.py:216 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" -#: bpython/repl.py:477 +#: bpython/repl.py:513 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:482 +#: bpython/repl.py:518 #, python-format msgid "Cannot get source: %s" msgstr "Impossible de récupérer le source: %s" -#: bpython/repl.py:487 +#: bpython/repl.py:523 #, python-format msgid "Cannot access source of %r" msgstr "Impossible d'accéder au source de %r" -#: bpython/repl.py:489 +#: bpython/repl.py:525 #, python-format msgid "No source code found for %s" msgstr "Pas de code source trouvé pour %s" -#: bpython/repl.py:655 +#: bpython/repl.py:652 +msgid "Save to file (Esc to cancel): " +msgstr "" + +#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +msgid "Save cancelled." +msgstr "" + +#: bpython/repl.py:667 +#, python-format +msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " +msgstr "" + +#: bpython/repl.py:671 +msgid "overwrite" +msgstr "" + +#: bpython/repl.py:673 +msgid "append" +msgstr "" + +#: bpython/repl.py:685 bpython/repl.py:1028 +#, python-format +msgid "Error writing file '%s': %s" +msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" + +#: bpython/repl.py:688 +#, python-format +msgid "Saved to %s." +msgstr "" + +#: bpython/repl.py:694 msgid "No clipboard available." msgstr "Pas de presse-papier disponible." -#: bpython/repl.py:662 +#: bpython/repl.py:701 msgid "Could not copy to clipboard." msgstr "Impossible de copier vers le presse-papier." -#: bpython/repl.py:664 +#: bpython/repl.py:703 msgid "Copied content to clipboard." msgstr "Contenu copié vers le presse-papier." -#: bpython/repl.py:673 +#: bpython/repl.py:712 msgid "Pastebin buffer? (y/N) " msgstr "Tampon Pastebin ? (o/N) " -#: bpython/repl.py:674 -msgid "Pastebin aborted" -msgstr "Pastebin abandonné" +#: bpython/repl.py:713 +msgid "Pastebin aborted." +msgstr "Pastebin abandonné." -#: bpython/repl.py:681 +#: bpython/repl.py:720 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" -#: bpython/repl.py:700 bpython/repl.py:728 +#: bpython/repl.py:739 bpython/repl.py:767 msgid "Posting data to pastebin..." msgstr "Envoi des donnés à pastebin..." -#: bpython/repl.py:705 +#: bpython/repl.py:744 #, python-format msgid "Upload failed: %s" msgstr "Echec du téléchargement: %s" -#: bpython/repl.py:721 +#: bpython/repl.py:760 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "URL Pastebin: %s - URL de suppression: %s" -#: bpython/repl.py:740 +#: bpython/repl.py:779 msgid "Upload failed: Helper program not found." msgstr "Echec de l'upload: programme externe non trouvé." -#: bpython/repl.py:743 +#: bpython/repl.py:782 msgid "Upload failed: Helper program could not be run." msgstr "Echec de l'upload: impossible de lancer le programme externe." -#: bpython/repl.py:750 +#: bpython/repl.py:789 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." -msgstr "Echec de l'upload: le programme externe a renvoyé un statut de sortie différent de zéro %s." +msgstr "" +"Echec de l'upload: le programme externe a renvoyé un statut de sortie " +"différent de zéro %s." -#: bpython/repl.py:754 +#: bpython/repl.py:793 msgid "Upload failed: No output from helper program." msgstr "Echec de l'upload: pas de sortie du programme externe." -#: bpython/repl.py:761 -msgid "Upload failed: Failed to recognize the helper program's output as an URL." -msgstr "Echec de l'upload: la sortie du programme externe ne correspond pas à une URL." +#: bpython/repl.py:800 +msgid "" +"Upload failed: Failed to recognize the helper program's output as an URL." +msgstr "" +"Echec de l'upload: la sortie du programme externe ne correspond pas à une " +"URL." -#: bpython/repl.py:767 +#: bpython/repl.py:806 #, python-format msgid "Pastebin URL: %s" msgstr "URL Pastebin: %s" -#: bpython/repl.py:953 +#: bpython/repl.py:839 +#, python-format +msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" +msgstr "" + +#: bpython/repl.py:846 bpython/repl.py:850 +msgid "Undo canceled" +msgstr "" + +#: bpython/repl.py:853 +#, python-format +msgid "Undoing 1 line... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:856 +#, python-format +msgid "Undoing %d lines... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:1018 msgid "Config file does not exist - create new from default? (y/N)" -msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" +msgstr "" +"Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" + +#: bpython/repl.py:1035 +msgid "bpython config file edited. Restart bpython for changes to take effect." +msgstr "" + +#: bpython/repl.py:1038 +msgid "Error editing config file." +msgstr "" #: bpython/urwid.py:617 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " -msgstr " <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> Montrer Source " +msgstr "" +" <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> Montrer " +"Source " #: bpython/urwid.py:1126 msgid "Run twisted reactor." @@ -181,7 +251,8 @@ msgstr "Lancer le reactor twisted." #: bpython/urwid.py:1128 msgid "Select specific reactor (see --help-reactors). Implies --twisted." -msgstr "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." +msgstr "" +"Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." #: bpython/urwid.py:1131 msgid "List available reactors for -r." @@ -192,23 +263,23 @@ msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" pour donner " -"plus d'options au plugin." +"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" pour " +"donner plus d'options au plugin." #: bpython/urwid.py:1136 msgid "Port to run an eval server on (forces Twisted)." msgstr "Port pour lancer un server eval (force Twisted)." -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." -#: bpython/curtsiesfrontend/repl.py:470 +#: bpython/curtsiesfrontend/repl.py:472 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index 35a2ab832..352bd336a 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-01-23 23:19+0100\n" -"PO-Revision-Date: 2015-01-23 23:20+0100\n" +"POT-Creation-Date: 2015-02-01 23:02+0100\n" +"PO-Revision-Date: 2015-02-01 23:04+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: Michele Orrù\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -40,31 +40,31 @@ msgstr "" msgid "Print version and exit." msgstr "" -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "y" msgstr "s" -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "yes" msgstr "si" -#: bpython/cli.py:1696 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1696 msgid "Save" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1700 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "" @@ -76,99 +76,157 @@ msgstr "" msgid "enter lines of file as though interactively typed" msgstr "" -#: bpython/history.py:236 +#: bpython/history.py:216 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:477 +#: bpython/repl.py:513 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:482 +#: bpython/repl.py:518 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:487 +#: bpython/repl.py:523 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:489 +#: bpython/repl.py:525 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:655 +#: bpython/repl.py:652 +msgid "Save to file (Esc to cancel): " +msgstr "" + +#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +msgid "Save cancelled." +msgstr "" + +#: bpython/repl.py:667 +#, python-format +msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " +msgstr "" + +#: bpython/repl.py:671 +msgid "overwrite" +msgstr "" + +#: bpython/repl.py:673 +msgid "append" +msgstr "" + +#: bpython/repl.py:685 bpython/repl.py:1028 +#, python-format +msgid "Error writing file '%s': %s" +msgstr "" + +#: bpython/repl.py:688 +#, python-format +msgid "Saved to %s." +msgstr "" + +#: bpython/repl.py:694 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:701 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:664 +#: bpython/repl.py:703 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:673 +#: bpython/repl.py:712 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:674 -msgid "Pastebin aborted" +#: bpython/repl.py:713 +msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:681 +#: bpython/repl.py:720 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:700 bpython/repl.py:728 +#: bpython/repl.py:739 bpython/repl.py:767 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:705 +#: bpython/repl.py:744 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:721 +#: bpython/repl.py:760 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:740 +#: bpython/repl.py:779 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:743 +#: bpython/repl.py:782 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:750 +#: bpython/repl.py:789 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:754 +#: bpython/repl.py:793 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:761 +#: bpython/repl.py:800 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:767 +#: bpython/repl.py:806 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:953 +#: bpython/repl.py:839 +#, python-format +msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" +msgstr "" + +#: bpython/repl.py:846 bpython/repl.py:850 +msgid "Undo canceled" +msgstr "" + +#: bpython/repl.py:853 +#, python-format +msgid "Undoing 1 line... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:856 +#, python-format +msgid "Undoing %d lines... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:1018 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" +#: bpython/repl.py:1035 +msgid "bpython config file edited. Restart bpython for changes to take effect." +msgstr "" + +#: bpython/repl.py:1038 +msgid "Error editing config file." +msgstr "" + #: bpython/urwid.py:617 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " @@ -196,16 +254,16 @@ msgstr "" msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:470 +#: bpython/curtsiesfrontend/repl.py:472 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index 04e31bd23..103b1ffec 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-01-23 23:19+0100\n" -"PO-Revision-Date: 2015-01-23 23:20+0100\n" +"POT-Creation-Date: 2015-02-01 23:02+0100\n" +"PO-Revision-Date: 2015-02-01 23:04+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -40,31 +40,31 @@ msgstr "" msgid "Print version and exit." msgstr "" -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "y" msgstr "j" -#: bpython/cli.py:321 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:555 msgid "yes" msgstr "ja" -#: bpython/cli.py:1696 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1696 msgid "Save" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1700 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "" @@ -76,99 +76,157 @@ msgstr "" msgid "enter lines of file as though interactively typed" msgstr "" -#: bpython/history.py:236 +#: bpython/history.py:216 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:477 +#: bpython/repl.py:513 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:482 +#: bpython/repl.py:518 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:487 +#: bpython/repl.py:523 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:489 +#: bpython/repl.py:525 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:655 +#: bpython/repl.py:652 +msgid "Save to file (Esc to cancel): " +msgstr "" + +#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +msgid "Save cancelled." +msgstr "" + +#: bpython/repl.py:667 +#, python-format +msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " +msgstr "" + +#: bpython/repl.py:671 +msgid "overwrite" +msgstr "" + +#: bpython/repl.py:673 +msgid "append" +msgstr "" + +#: bpython/repl.py:685 bpython/repl.py:1028 +#, python-format +msgid "Error writing file '%s': %s" +msgstr "" + +#: bpython/repl.py:688 +#, python-format +msgid "Saved to %s." +msgstr "" + +#: bpython/repl.py:694 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:701 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:664 +#: bpython/repl.py:703 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:673 +#: bpython/repl.py:712 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:674 -msgid "Pastebin aborted" +#: bpython/repl.py:713 +msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:681 +#: bpython/repl.py:720 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:700 bpython/repl.py:728 +#: bpython/repl.py:739 bpython/repl.py:767 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:705 +#: bpython/repl.py:744 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:721 +#: bpython/repl.py:760 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:740 +#: bpython/repl.py:779 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:743 +#: bpython/repl.py:782 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:750 +#: bpython/repl.py:789 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %s." msgstr "" -#: bpython/repl.py:754 +#: bpython/repl.py:793 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:761 +#: bpython/repl.py:800 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:767 +#: bpython/repl.py:806 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:953 +#: bpython/repl.py:839 +#, python-format +msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" +msgstr "" + +#: bpython/repl.py:846 bpython/repl.py:850 +msgid "Undo canceled" +msgstr "" + +#: bpython/repl.py:853 +#, python-format +msgid "Undoing 1 line... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:856 +#, python-format +msgid "Undoing %d lines... (est. %.1f seconds)" +msgstr "" + +#: bpython/repl.py:1018 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" +#: bpython/repl.py:1035 +msgid "bpython config file edited. Restart bpython for changes to take effect." +msgstr "" + +#: bpython/repl.py:1038 +msgid "Error editing config file." +msgstr "" + #: bpython/urwid.py:617 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " @@ -196,16 +254,16 @@ msgstr "" msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:258 +#: bpython/curtsiesfrontend/repl.py:259 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:470 +#: bpython/curtsiesfrontend/repl.py:472 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" From 2c841a8ad2153219c2b19ce5a48e4b67fff809ab Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 00:31:53 +0100 Subject: [PATCH 0319/1650] Use plural strings Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 14 ++--- bpython/translations/__init__.py | 5 ++ bpython/translations/bpython.pot | 23 ++++---- .../translations/de/LC_MESSAGES/bpython.po | 50 +++++++--------- .../translations/es_ES/LC_MESSAGES/bpython.po | 23 ++++---- .../translations/fr_FR/LC_MESSAGES/bpython.po | 59 ++++++++----------- .../translations/it_IT/LC_MESSAGES/bpython.po | 23 ++++---- .../translations/nl_NL/LC_MESSAGES/bpython.po | 23 ++++---- 8 files changed, 98 insertions(+), 122 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index bbf4b85a5..c4d1c950a 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -48,7 +48,7 @@ from bpython import inspection from bpython._py3compat import PythonLexer, py3 from bpython.formatter import Parenthesis -from bpython.translations import _ +from bpython.translations import _, ngettext from bpython.clipboard import get_clipboard, CopyFailed from bpython.history import History import bpython.autocomplete as autocomplete @@ -666,7 +666,6 @@ def write2file(self): if os.path.exists(fn): mode = self.interact.file_prompt(_('%s already exists. Do you ' 'want to (c)ancel, ' - 'wantch to (c)ancel, ' ' (o)verwrite or ' '(a)ppend? ') % (fn, )) if mode in ('o', 'overwrite', _('overwrite')): @@ -850,12 +849,10 @@ def prompt_undo(self): if n == 0: self.interact.notify(_('Undo canceled'), .1) return 0 - if n == 1: - self.interact.notify(_('Undoing 1 line... (est. %.1f seconds)') - % (est,), .1) else: - self.interact.notify(_('Undoing %d lines... (est. %.1f seconds)') - % (n, est), .1) + message = ngettext('Undoing %d line... (est. %.1f seconds)', + 'Undoing %d lines... (est. %.1f seconds)', n) + self.interact.notify(message % (n, est), .1) return n def undo(self, n=1): @@ -994,7 +991,8 @@ def clear_current_line(self): It prevents autoindentation from occuring after a traceback.""" def send_to_external_editor(self, text, filename=None): - """Returns modified text from an editor, or the oriignal text if editor exited with non-zero""" + """Returns modified text from an editor, or the oriignal text if editor + exited with non-zero""" editor_args = shlex.split(self.config.editor) with tempfile.NamedTemporaryFile(suffix='.py') as temp: temp.write(text.encode(getpreferredencoding())) diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index b98dcea4f..436714f2c 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -11,10 +11,15 @@ if py3: def _(message): return translator.gettext(message) + + def ngettext(singular, plural, n): + return translator.ngettext(singular, plural, n) else: def _(message): return translator.ugettext(message) + def ngettext(singular, plural, n): + return translator.ungettext(singular, plural, n) def init(locale_dir=None, languages=None): try: diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 8325bed18..3421109b1 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.13-492\n" +"Project-Id-Version: bpython 0.13-493\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-02-01 23:02+0100\n" +"POT-Creation-Date: 2015-02-02 00:30+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -121,7 +121,7 @@ msgstr "" msgid "append" msgstr "" -#: bpython/repl.py:685 bpython/repl.py:1028 +#: bpython/repl.py:685 bpython/repl.py:1027 #, python-format msgid "Error writing file '%s': %s" msgstr "" @@ -207,23 +207,20 @@ msgstr "" #: bpython/repl.py:853 #, python-format -msgid "Undoing 1 line... (est. %.1f seconds)" -msgstr "" - -#: bpython/repl.py:856 -#, python-format -msgid "Undoing %d lines... (est. %.1f seconds)" -msgstr "" +msgid "Undoing %d line... (est. %.1f seconds)" +msgid_plural "Undoing %d lines... (est. %.1f seconds)" +msgstr[0] "" +msgstr[1] "" -#: bpython/repl.py:1018 +#: bpython/repl.py:1017 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1035 +#: bpython/repl.py:1034 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1038 +#: bpython/repl.py:1037 msgid "Error editing config file." msgstr "" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 45f2e5b4d..9e53013f2 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,23 +7,21 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-01 23:02+0100\n" -"PO-Revision-Date: 2015-02-01 23:02+0100\n" +"POT-Creation-Date: 2015-02-02 00:30+0100\n" +"PO-Revision-Date: 2015-02-02 00:31+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: de \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -"Language: de\n" -"X-Generator: Poedit 1.6.10\n" #: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back to " -"the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back " +"to the regular Python interpreter." msgstr "" #: bpython/args.py:67 @@ -113,8 +111,7 @@ msgstr "Speichern abgebrochen." #: bpython/repl.py:667 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " -msgstr "" -"%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" +msgstr "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" #: bpython/repl.py:671 msgid "overwrite" @@ -124,7 +121,7 @@ msgstr "überschreiben" msgid "append" msgstr "anhängen" -#: bpython/repl.py:685 bpython/repl.py:1028 +#: bpython/repl.py:685 bpython/repl.py:1027 #, python-format msgid "Error writing file '%s': %s" msgstr "Fehler beim Schreiben in Datei '%s': %s" @@ -175,13 +172,13 @@ msgstr "" #: bpython/repl.py:779 msgid "Upload failed: Helper program not found." -msgstr "" -"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht gefunden werden." +msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht gefunden werden." #: bpython/repl.py:782 msgid "Upload failed: Helper program could not be run." msgstr "" -"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht ausgeführt werden." +"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht ausgeführt " +"werden." #: bpython/repl.py:789 #, python-format @@ -190,12 +187,10 @@ msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm beendete mit Status %s." #: bpython/repl.py:793 msgid "Upload failed: No output from helper program." -msgstr "" -"Hochladen ist fehlgeschlagen: Keine Ausgabe von Hilfsprogramm vorhanden." +msgstr "Hochladen ist fehlgeschlagen: Keine Ausgabe von Hilfsprogramm vorhanden." #: bpython/repl.py:800 -msgid "" -"Upload failed: Failed to recognize the helper program's output as an URL." +msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" "Hochladen ist fehlgeschlagen: Konte Ausgabe von Hilfsprogramm nicht " "verarbeiten." @@ -216,27 +211,24 @@ msgstr "Rückgängigmachen abgebrochen" #: bpython/repl.py:853 #, python-format -msgid "Undoing 1 line... (est. %.1f seconds)" -msgstr "" - -#: bpython/repl.py:856 -#, python-format -msgid "Undoing %d lines... (est. %.1f seconds)" -msgstr "" +msgid "Undoing %d line... (est. %.1f seconds)" +msgid_plural "Undoing %d lines... (est. %.1f seconds)" +msgstr[0] "" +msgstr[1] "" -#: bpython/repl.py:1018 +#: bpython/repl.py:1017 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt werden? " -"(j/N)" +"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " +"werden? (j/N)" -#: bpython/repl.py:1035 +#: bpython/repl.py:1034 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" "bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " "Änderungen übernommen werden." -#: bpython/repl.py:1038 +#: bpython/repl.py:1037 msgid "Error editing config file." msgstr "Fehler beim Bearbeiten der Konfigurationsdatei." diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index cec821d93..4d634ca93 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-01 23:02+0100\n" -"PO-Revision-Date: 2015-02-01 23:04+0100\n" +"POT-Creation-Date: 2015-02-02 00:30+0100\n" +"PO-Revision-Date: 2015-02-02 00:31+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -121,7 +121,7 @@ msgstr "" msgid "append" msgstr "" -#: bpython/repl.py:685 bpython/repl.py:1028 +#: bpython/repl.py:685 bpython/repl.py:1027 #, python-format msgid "Error writing file '%s': %s" msgstr "" @@ -207,23 +207,20 @@ msgstr "" #: bpython/repl.py:853 #, python-format -msgid "Undoing 1 line... (est. %.1f seconds)" -msgstr "" - -#: bpython/repl.py:856 -#, python-format -msgid "Undoing %d lines... (est. %.1f seconds)" -msgstr "" +msgid "Undoing %d line... (est. %.1f seconds)" +msgid_plural "Undoing %d lines... (est. %.1f seconds)" +msgstr[0] "" +msgstr[1] "" -#: bpython/repl.py:1018 +#: bpython/repl.py:1017 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1035 +#: bpython/repl.py:1034 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1038 +#: bpython/repl.py:1037 msgid "Error editing config file." msgstr "" diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index 16e63ab60..abb971019 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,27 +6,25 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-01 23:02+0100\n" -"PO-Revision-Date: 2015-02-01 23:03+0100\n" +"POT-Creation-Date: 2015-02-02 00:30+0100\n" +"PO-Revision-Date: 2015-02-02 00:31+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Plural-Forms: nplurals=2; plural=(n > 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -"Language: fr_FR\n" -"X-Generator: Poedit 1.6.10\n" #: bpython/args.py:57 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back to " -"the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back " +"to the regular Python interpreter." msgstr "" "Utilisation: %prog [options] [fichier [arguments]]\n" -"NOTE: Si bpython ne reconnaît pas un des arguments fournis, l'interpréteur " -"Python classique sera lancé" +"NOTE: Si bpython ne reconnaît pas un des arguments fournis, " +"l'interpréteur Python classique sera lancé" #: bpython/args.py:67 msgid "Use CONFIG instead of default config file." @@ -35,7 +33,8 @@ msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." #: bpython/args.py:69 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -"Aller dans le shell bpython après l'exécution du fichier au lieu de quitter." +"Aller dans le shell bpython après l'exécution du fichier au lieu de " +"quitter." #: bpython/args.py:72 msgid "Don't flush the output to stdout." @@ -126,7 +125,7 @@ msgstr "" msgid "append" msgstr "" -#: bpython/repl.py:685 bpython/repl.py:1028 +#: bpython/repl.py:685 bpython/repl.py:1027 #, python-format msgid "Error writing file '%s': %s" msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" @@ -195,11 +194,10 @@ msgid "Upload failed: No output from helper program." msgstr "Echec de l'upload: pas de sortie du programme externe." #: bpython/repl.py:800 -msgid "" -"Upload failed: Failed to recognize the helper program's output as an URL." +msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -"Echec de l'upload: la sortie du programme externe ne correspond pas à une " -"URL." +"Echec de l'upload: la sortie du programme externe ne correspond pas à une" +" URL." #: bpython/repl.py:806 #, python-format @@ -217,24 +215,20 @@ msgstr "" #: bpython/repl.py:853 #, python-format -msgid "Undoing 1 line... (est. %.1f seconds)" -msgstr "" +msgid "Undoing %d line... (est. %.1f seconds)" +msgid_plural "Undoing %d lines... (est. %.1f seconds)" +msgstr[0] "" +msgstr[1] "" -#: bpython/repl.py:856 -#, python-format -msgid "Undoing %d lines... (est. %.1f seconds)" -msgstr "" - -#: bpython/repl.py:1018 +#: bpython/repl.py:1017 msgid "Config file does not exist - create new from default? (y/N)" -msgstr "" -"Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" +msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" -#: bpython/repl.py:1035 +#: bpython/repl.py:1034 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1038 +#: bpython/repl.py:1037 msgid "Error editing config file." msgstr "" @@ -242,8 +236,8 @@ msgstr "" #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -" <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> Montrer " -"Source " +" <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> " +"Montrer Source " #: bpython/urwid.py:1126 msgid "Run twisted reactor." @@ -251,8 +245,7 @@ msgstr "Lancer le reactor twisted." #: bpython/urwid.py:1128 msgid "Select specific reactor (see --help-reactors). Implies --twisted." -msgstr "" -"Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." +msgstr "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." #: bpython/urwid.py:1131 msgid "List available reactors for -r." @@ -263,8 +256,8 @@ msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" pour " -"donner plus d'options au plugin." +"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" " +"pour donner plus d'options au plugin." #: bpython/urwid.py:1136 msgid "Port to run an eval server on (forces Twisted)." diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index 352bd336a..69bb04c7a 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-01 23:02+0100\n" -"PO-Revision-Date: 2015-02-01 23:04+0100\n" +"POT-Creation-Date: 2015-02-02 00:30+0100\n" +"PO-Revision-Date: 2015-02-02 00:31+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: Michele Orrù\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -121,7 +121,7 @@ msgstr "" msgid "append" msgstr "" -#: bpython/repl.py:685 bpython/repl.py:1028 +#: bpython/repl.py:685 bpython/repl.py:1027 #, python-format msgid "Error writing file '%s': %s" msgstr "" @@ -207,23 +207,20 @@ msgstr "" #: bpython/repl.py:853 #, python-format -msgid "Undoing 1 line... (est. %.1f seconds)" -msgstr "" - -#: bpython/repl.py:856 -#, python-format -msgid "Undoing %d lines... (est. %.1f seconds)" -msgstr "" +msgid "Undoing %d line... (est. %.1f seconds)" +msgid_plural "Undoing %d lines... (est. %.1f seconds)" +msgstr[0] "" +msgstr[1] "" -#: bpython/repl.py:1018 +#: bpython/repl.py:1017 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1035 +#: bpython/repl.py:1034 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1038 +#: bpython/repl.py:1037 msgid "Error editing config file." msgstr "" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index 103b1ffec..8840c93ba 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-01 23:02+0100\n" -"PO-Revision-Date: 2015-02-01 23:04+0100\n" +"POT-Creation-Date: 2015-02-02 00:30+0100\n" +"PO-Revision-Date: 2015-02-02 00:31+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -121,7 +121,7 @@ msgstr "" msgid "append" msgstr "" -#: bpython/repl.py:685 bpython/repl.py:1028 +#: bpython/repl.py:685 bpython/repl.py:1027 #, python-format msgid "Error writing file '%s': %s" msgstr "" @@ -207,23 +207,20 @@ msgstr "" #: bpython/repl.py:853 #, python-format -msgid "Undoing 1 line... (est. %.1f seconds)" -msgstr "" - -#: bpython/repl.py:856 -#, python-format -msgid "Undoing %d lines... (est. %.1f seconds)" -msgstr "" +msgid "Undoing %d line... (est. %.1f seconds)" +msgid_plural "Undoing %d lines... (est. %.1f seconds)" +msgstr[0] "" +msgstr[1] "" -#: bpython/repl.py:1018 +#: bpython/repl.py:1017 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1035 +#: bpython/repl.py:1034 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1038 +#: bpython/repl.py:1037 msgid "Error editing config file." msgstr "" From 4854ecb325690643a054b5480b95967e4fdf1b3c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 17:35:17 +0100 Subject: [PATCH 0320/1650] More translatable strings Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 17 +++++++---- bpython/repl.py | 2 +- bpython/translations/bpython.pot | 28 ++++++++++++++++-- .../translations/de/LC_MESSAGES/bpython.po | 29 +++++++++++++++++-- .../translations/es_ES/LC_MESSAGES/bpython.po | 29 +++++++++++++++++-- .../translations/fr_FR/LC_MESSAGES/bpython.po | 29 +++++++++++++++++-- .../translations/it_IT/LC_MESSAGES/bpython.po | 29 +++++++++++++++++-- .../translations/nl_NL/LC_MESSAGES/bpython.po | 29 +++++++++++++++++-- 8 files changed, 167 insertions(+), 25 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index bbc318df2..3387da0ca 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -485,10 +485,13 @@ def process_control_event(self, e): elif isinstance(e, bpythonevents.ReloadEvent): if self.watching_files: self.clear_modules_and_reevaluate() - self.status_bar.message('Reloaded at ' + time.strftime('%H:%M:%S') + ' because ' + ' & '.join(e.files_modified) + ' modified') + self.status_bar.message( + _('Reloaded at %s because %s modified.') % ( + time.strftime('%X'), + ' & '.join(e.files_modified))) else: - raise ValueError("don't know how to handle this event type: %r" % e) + raise ValueError("Don't know how to handle event type: %r" % e) def process_key_event(self, e): # To find the curtsies name for a keypress, try python -m curtsies.events @@ -739,22 +742,24 @@ def clear_modules_and_reevaluate(self): del sys.modules[modname] self.reevaluate(insert_into_history=True) self.cursor_offset, self.current_line = cursor, line - self.status_bar.message('Reloaded at ' + time.strftime('%H:%M:%S') + ' by user') + self.status_bar.message(_('Reloaded at %s by user.') % \ + (time.strftime('%X'), )) def toggle_file_watch(self): if self.watcher: if self.watching_files: - msg = "Auto-reloading deactivated" + msg = _("Auto-reloading deactivated.") self.status_bar.message(msg) self.watcher.deactivate() self.watching_files = False else: - msg = "Auto-reloading active, watching for file changes..." + msg = _("Auto-reloading active, watching for file changes...") self.status_bar.message(msg) self.watching_files = True self.watcher.activate() else: - self.status_bar.message('Autoreloading not available because watchdog not installed') + self.status_bar.message(_('Auto-reloading not available because ' + 'watchdog not installed.')) ## Handler Helpers def add_normal_character(self, char): diff --git a/bpython/repl.py b/bpython/repl.py index c4d1c950a..31bc1894b 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -786,7 +786,7 @@ def do_pastebin_helper(self, s): if helper.returncode != 0: self.interact.notify(_('Upload failed: ' 'Helper program returned non-zero exit ' - 'status %s.' % (helper.returncode, ))) + 'status %d.' % (helper.returncode, ))) return if not paste_url: diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 3421109b1..c581cf0be 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.13-493\n" +"Project-Id-Version: bpython 0.13-494\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-02-02 00:30+0100\n" +"POT-Creation-Date: 2015-02-02 17:34+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -180,7 +180,7 @@ msgstr "" #: bpython/repl.py:789 #, python-format -msgid "Upload failed: Helper program returned non-zero exit status %s." +msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "" #: bpython/repl.py:793 @@ -265,3 +265,25 @@ msgstr "" msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" +#: bpython/curtsiesfrontend/repl.py:489 +#, python-format +msgid "Reloaded at %s because %s modified." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:745 +#, python-format +msgid "Reloaded at %s by user." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:751 +msgid "Auto-reloading deactivated." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:756 +msgid "Auto-reloading active, watching for file changes..." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:761 +msgid "Auto-reloading not available because watchdog not installed." +msgstr "" + diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 9e53013f2..941a867b0 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-02 00:30+0100\n" +"POT-Creation-Date: 2015-02-02 17:34+0100\n" "PO-Revision-Date: 2015-02-02 00:31+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: de \n" @@ -181,8 +181,8 @@ msgstr "" "werden." #: bpython/repl.py:789 -#, python-format -msgid "Upload failed: Helper program returned non-zero exit status %s." +#, fuzzy, python-format +msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm beendete mit Status %s." #: bpython/repl.py:793 @@ -272,3 +272,26 @@ msgstr "Drücke <%s> für Hilfe." #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" + +#: bpython/curtsiesfrontend/repl.py:489 +#, python-format +msgid "Reloaded at %s because %s modified." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:745 +#, python-format +msgid "Reloaded at %s by user." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:751 +msgid "Auto-reloading deactivated." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:756 +msgid "Auto-reloading active, watching for file changes..." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:761 +msgid "Auto-reloading not available because watchdog not installed." +msgstr "" + diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 4d634ca93..01aee8886 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-02 00:30+0100\n" -"PO-Revision-Date: 2015-02-02 00:31+0100\n" +"POT-Creation-Date: 2015-02-02 17:34+0100\n" +"PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -180,7 +180,7 @@ msgstr "" #: bpython/repl.py:789 #, python-format -msgid "Upload failed: Helper program returned non-zero exit status %s." +msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "" #: bpython/repl.py:793 @@ -266,3 +266,26 @@ msgstr "" #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" + +#: bpython/curtsiesfrontend/repl.py:489 +#, python-format +msgid "Reloaded at %s because %s modified." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:745 +#, python-format +msgid "Reloaded at %s by user." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:751 +msgid "Auto-reloading deactivated." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:756 +msgid "Auto-reloading active, watching for file changes..." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:761 +msgid "Auto-reloading not available because watchdog not installed." +msgstr "" + diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index abb971019..940900462 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-02 00:30+0100\n" +"POT-Creation-Date: 2015-02-02 17:34+0100\n" "PO-Revision-Date: 2015-02-02 00:31+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" @@ -183,8 +183,8 @@ msgid "Upload failed: Helper program could not be run." msgstr "Echec de l'upload: impossible de lancer le programme externe." #: bpython/repl.py:789 -#, python-format -msgid "Upload failed: Helper program returned non-zero exit status %s." +#, fuzzy, python-format +msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "" "Echec de l'upload: le programme externe a renvoyé un statut de sortie " "différent de zéro %s." @@ -276,3 +276,26 @@ msgstr "Appuyer sur <%s> pour de l'aide." #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" + +#: bpython/curtsiesfrontend/repl.py:489 +#, python-format +msgid "Reloaded at %s because %s modified." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:745 +#, python-format +msgid "Reloaded at %s by user." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:751 +msgid "Auto-reloading deactivated." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:756 +msgid "Auto-reloading active, watching for file changes..." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:761 +msgid "Auto-reloading not available because watchdog not installed." +msgstr "" + diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index 69bb04c7a..92a49c8ac 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-02 00:30+0100\n" -"PO-Revision-Date: 2015-02-02 00:31+0100\n" +"POT-Creation-Date: 2015-02-02 17:34+0100\n" +"PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: Michele Orrù\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -180,7 +180,7 @@ msgstr "" #: bpython/repl.py:789 #, python-format -msgid "Upload failed: Helper program returned non-zero exit status %s." +msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "" #: bpython/repl.py:793 @@ -264,3 +264,26 @@ msgstr "" #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" + +#: bpython/curtsiesfrontend/repl.py:489 +#, python-format +msgid "Reloaded at %s because %s modified." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:745 +#, python-format +msgid "Reloaded at %s by user." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:751 +msgid "Auto-reloading deactivated." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:756 +msgid "Auto-reloading active, watching for file changes..." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:761 +msgid "Auto-reloading not available because watchdog not installed." +msgstr "" + diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index 8840c93ba..2bc24908c 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-02 00:30+0100\n" -"PO-Revision-Date: 2015-02-02 00:31+0100\n" +"POT-Creation-Date: 2015-02-02 17:34+0100\n" +"PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -180,7 +180,7 @@ msgstr "" #: bpython/repl.py:789 #, python-format -msgid "Upload failed: Helper program returned non-zero exit status %s." +msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "" #: bpython/repl.py:793 @@ -264,3 +264,26 @@ msgstr "" #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" + +#: bpython/curtsiesfrontend/repl.py:489 +#, python-format +msgid "Reloaded at %s because %s modified." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:745 +#, python-format +msgid "Reloaded at %s by user." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:751 +msgid "Auto-reloading deactivated." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:756 +msgid "Auto-reloading active, watching for file changes..." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:761 +msgid "Auto-reloading not available because watchdog not installed." +msgstr "" + From b66b243540d9bf5a5be456f7c269d8d9a328bf7e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 20:08:30 +0100 Subject: [PATCH 0321/1650] Fix sample-config path --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3387da0ca..f8f98f6de 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1415,7 +1415,7 @@ def version_help_text(self): return (('bpython-curtsies version %s' % bpython.__version__) + ' ' + ('using curtsies version %s' % curtsies.__version__) + '\n' + HELP_MESSAGE.format(config_file_location=default_config_path(), - example_config_url='https://raw.githubusercontent.com/bpython/bpython/master/sample-config', + example_config_url='https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config', config=self.config) ) From 82a1dea7e1c3cb92d7b321a2db53e0ce8de6a57d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 21:12:51 +0100 Subject: [PATCH 0322/1650] Remove unused function Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/parse.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index e7e0bc042..aa87ba2df 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -78,8 +78,3 @@ def peel_off_string(s): rest = d['rest'] del d['rest'] return d, rest - -def string_to_fmtstr(x): - config = Struct() - loadini(config, default_config_path()) - return parse(format(PythonLexer().get_tokens(x), BPythonFormatter(config.color_scheme))) From 45b373a0fcca01519d2fb53b231b570f38b1e912 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 21:13:09 +0100 Subject: [PATCH 0323/1650] Remove useless use of list Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/parse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index aa87ba2df..d5b2550b8 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -11,7 +11,7 @@ import re -cnames = dict(list(zip('krgybmcwd', colors + ('default',)))) +cnames = dict(zip('krgybmcwd', colors + ('default',))) def func_for_letter(l, default='k'): """Returns FmtStr constructor for a bpython-style color code""" @@ -35,7 +35,7 @@ def parse(s): break start, rest = peel_off_string(rest) stuff.append(start) - return (sum([fs_from_match(d) for d in stuff[1:]], fs_from_match(stuff[0])) + return (sum((fs_from_match(d) for d in stuff[1:]), fs_from_match(stuff[0])) if len(stuff) > 0 else FmtStr()) From a902f10c60b85860651129c6a1c6b75b3e8d4217 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 22:20:20 +0100 Subject: [PATCH 0324/1650] Use isupper Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/parse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index d5b2550b8..1e7f22de2 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -19,7 +19,7 @@ def func_for_letter(l, default='k'): l = default elif l == 'D': l = default.upper() - return partial(fmtstr, fg=cnames[l.lower()], bold=(l.lower() != l)) + return partial(fmtstr, fg=cnames[l.lower()], bold=l.isupper()) def color_for_letter(l, default='k'): if l == 'd': @@ -44,7 +44,7 @@ def fs_from_match(d): if d['fg']: # this isn't according to spec as I understand it - if d['fg'] != d['fg'].lower(): + if d['fg'].isupper(): d['bold'] = True #TODO figure out why boldness isn't based on presence of \x02 From 1e8c8931d5700420d2bf60c7ee9be68882bdd219 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 22:23:35 +0100 Subject: [PATCH 0325/1650] Compile regex Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/parse.py | 25 +++++++++++++++---------- bpython/line.py | 5 +++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 1e7f22de2..e62be5cc0 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -1,4 +1,5 @@ +from bpython.line import LazyReCompile from bpython.formatter import BPythonFormatter from bpython._py3compat import PythonLexer from bpython.config import Struct, loadini, default_config_path @@ -62,17 +63,21 @@ def fs_from_match(d): atts['bold'] = True return fmtstr(d['string'], **atts) + +peel_off_string_re = LazyReCompile( + r"""(?P\x01 + (?P[krgybmcwdKRGYBMCWD]?) + (?P[krgybmcwdKRGYBMCWDI]?)?) + (?P\x02?) + \x03 + (?P[^\x04]*) + \x04 + (?P.*) + """, re.VERBOSE | re.DOTALL) + + def peel_off_string(s): - p = r"""(?P\x01 - (?P[krgybmcwdKRGYBMCWD]?) - (?P[krgybmcwdKRGYBMCWDI]?)?) - (?P\x02?) - \x03 - (?P[^\x04]*) - \x04 - (?P.*) - """ - m = re.match(p, s, re.VERBOSE | re.DOTALL) + m = peel_off_string_re.match(s) assert m, repr(s) d = m.groupdict() rest = d['rest'] diff --git a/bpython/line.py b/bpython/line.py index 913364e47..75f48cd4b 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -10,14 +10,15 @@ class LazyReCompile(object): - def __init__(self, regex): + def __init__(self, regex, flags=0): self.regex = regex + self.flags = flags self.compiled = None def compile_regex(method): def _impl(self, *args, **kwargs): if self.compiled is None: - self.compiled = re.compile(self.regex) + self.compiled = re.compile(self.regex, self.flags) return method(self, *args, **kwargs) return _impl From cd46e2a611760dec01e34f5f6bb86aef9b231540 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 22:56:16 +0100 Subject: [PATCH 0326/1650] Move LazyReCompile to bpython.lazyre Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 3 +- bpython/curtsiesfrontend/parse.py | 2 +- bpython/lazyre.py | 50 +++++++++++++++++++++++++++++++ bpython/line.py | 29 +----------------- 4 files changed, 54 insertions(+), 30 deletions(-) create mode 100644 bpython/lazyre.py diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 7ab5eae59..4f6fafbb1 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -34,6 +34,7 @@ from bpython import importcompletion from bpython import line as lineparts from bpython._py3compat import py3 +from bpython.lazyre import LazyReCompile # Autocomplete modes @@ -460,7 +461,7 @@ def safe_eval(expr, namespace): raise EvaluationError -attr_matches_re = lineparts.LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") +attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") def attr_matches(text, namespace): diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index e62be5cc0..ee29c9eb1 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -1,5 +1,5 @@ -from bpython.line import LazyReCompile +from bpython.lazyre import LazyReCompile from bpython.formatter import BPythonFormatter from bpython._py3compat import PythonLexer from bpython.config import Struct, loadini, default_config_path diff --git a/bpython/lazyre.py b/bpython/lazyre.py new file mode 100644 index 000000000..2f85f367c --- /dev/null +++ b/bpython/lazyre.py @@ -0,0 +1,50 @@ +# The MIT License +# +# Copyright (c) 2015 the bpython authors. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import re + + +class LazyReCompile(object): + + def __init__(self, regex, flags=0): + self.regex = regex + self.flags = flags + self.compiled = None + + def compile_regex(method): + def _impl(self, *args, **kwargs): + if self.compiled is None: + self.compiled = re.compile(self.regex, self.flags) + return method(self, *args, **kwargs) + return _impl + + @compile_regex + def finditer(self, *args, **kwargs): + return self.compiled.finditer(*args, **kwargs) + + @compile_regex + def search(self, *args, **kwargs): + return self.compiled.search(*args, **kwargs) + + @compile_regex + def match(self, *args, **kwargs): + return self.compiled.match(*args, **kwargs) diff --git a/bpython/line.py b/bpython/line.py index 75f48cd4b..a565d50b9 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -4,35 +4,8 @@ Python code, and return None, or a tuple of the start index, end index, and the word.""" -import re from itertools import chain - - -class LazyReCompile(object): - - def __init__(self, regex, flags=0): - self.regex = regex - self.flags = flags - self.compiled = None - - def compile_regex(method): - def _impl(self, *args, **kwargs): - if self.compiled is None: - self.compiled = re.compile(self.regex, self.flags) - return method(self, *args, **kwargs) - return _impl - - @compile_regex - def finditer(self, *args, **kwargs): - return self.compiled.finditer(*args, **kwargs) - - @compile_regex - def search(self, *args, **kwargs): - return self.compiled.search(*args, **kwargs) - - @compile_regex - def match(self, *args, **kwargs): - return self.compiled.match(*args, **kwargs) +from bpython.lazyre import LazyReCompile current_word_re = LazyReCompile(r'[\w_][\w0-9._]*[(]?') From 73eae5e25b34eb0aef14ce1ac3406eeba5dba26a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 22:56:53 +0100 Subject: [PATCH 0327/1650] Typos Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/coderunner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 1bf377dae..a15317b7b 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -142,12 +142,12 @@ def run_code(self, for_code=None): raise SystemExitFromCodeGreenlet() def sigint_handler(self, *args): - """SIGINT handler to use while code is running or request being fufilled""" + """SIGINT handler to use while code is running or request being fulfilled""" if greenlet.getcurrent() is self.code_greenlet: logger.debug('sigint while running user code!') raise KeyboardInterrupt() else: - logger.debug('sigint while fufilling code request sigint handler running!') + logger.debug('sigint while fulfilling code request sigint handler running!') self.sigint_happened_in_main_greenlet = True def _blocking_run_code(self): From be5b8fbd12202936eabe4ad24c95abd5a8a99881 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 22:57:12 +0100 Subject: [PATCH 0328/1650] Remove unused imports Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/interpreter.py | 1 - bpython/curtsiesfrontend/parse.py | 4 ---- bpython/curtsiesfrontend/repl.py | 2 +- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 190ea6fcb..c8e6d150c 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,5 +1,4 @@ import code -import time import traceback import sys from pygments.token import Generic, Token, Keyword, Name, Comment, String diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index ee29c9eb1..c8d2f0ac5 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -1,13 +1,9 @@ from bpython.lazyre import LazyReCompile -from bpython.formatter import BPythonFormatter -from bpython._py3compat import PythonLexer -from bpython.config import Struct, loadini, default_config_path from curtsies.termformatconstants import FG_COLORS, BG_COLORS, colors from curtsies.formatstring import fmtstr, FmtStr -from pygments import format from functools import partial import re diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f8f98f6de..397ecf063 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -14,7 +14,7 @@ import unicodedata from pygments import format -from bpython._py3compat import PythonLexer, cast_unicode, cast_bytes +from bpython._py3compat import PythonLexer, cast_bytes from pygments.formatters import TerminalFormatter import blessings From 9d4308de558b865cb44eb79089bb0adf0bcbc078 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 23:15:42 +0100 Subject: [PATCH 0329/1650] More compiled regular expressions and less lists Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/manual_readline.py | 48 +++++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index d2e34126a..985bc6248 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -4,7 +4,7 @@ and the cursor location based on http://www.bigsmoke.us/readline/shortcuts""" -import re +from bpython.lazyre import LazyReCompile import inspect INDENT = 4 @@ -73,6 +73,7 @@ def __delitem__(self, key): elif key in self.cut_buffer_edits: del self.cut_buffer_edits[key] else: raise KeyError("key %r not mapped" % (key,)) + class UnconfiguredEdits(AbstractEdits): """Maps key to edit functions, and bins them by what parameters they take. @@ -114,6 +115,7 @@ def add_to_config(func): return func return add_to_config + class ConfiguredEdits(AbstractEdits): def __init__(self, simple_edits, cut_buffer_edits, awaiting_config, config, key_dispatch): self.simple_edits = dict(simple_edits) @@ -128,6 +130,7 @@ def add_config_attr(self, config_attr, func): def add(self, key, func): raise NotImplementedError("Config already set on this mapping") + edit_keys = UnconfiguredEdits() # Because the edits.on decorator runs the functions, functions which depend @@ -161,19 +164,21 @@ def beginning_of_line(cursor_offset, line): def end_of_line(cursor_offset, line): return len(line), line + +forward_word_re = LazyReCompile(r"\S\s") + + @edit_keys.on('') @edit_keys.on('') @edit_keys.on('') def forward_word(cursor_offset, line): - patt = r"\S\s" - match = re.search(patt, line[cursor_offset:]+' ') + match = forward_word_re.search(line[cursor_offset:]+' ') delta = match.end() - 1 if match else 0 return (cursor_offset + delta, line) def last_word_pos(string): """returns the start index of the last word of given string""" - patt = r'\S\s' - match = re.search(patt, string[::-1]) + match = forward_word_re.search(string[::-1]) index = match and len(string) - match.end() + 1 return index or 0 @@ -204,20 +209,29 @@ def backspace(cursor_offset, line): def delete_from_cursor_back(cursor_offset, line): return 0, line[cursor_offset:] + +delete_rest_of_word_re = LazyReCompile(r'\w\b') + + @edit_keys.on('') # option-d @kills_ahead def delete_rest_of_word(cursor_offset, line): - m = re.search(r'\w\b', line[cursor_offset:]) + m = delete_rest_of_word_re.search(line[cursor_offset:]) if not m: return cursor_offset, line, '' return (cursor_offset, line[:cursor_offset] + line[m.start()+cursor_offset+1:], line[cursor_offset:m.start()+cursor_offset+1]) + +delete_word_to_cursor_re = LazyReCompile(r'\s\S') + + @edit_keys.on(config='clear_word_key') @kills_behind def delete_word_to_cursor(cursor_offset, line): - matches = list(re.finditer(r'\s\S', line[:cursor_offset])) - start = matches[-1].start()+1 if matches else 0 + start = 0 + for match in delete_word_to_cursor_re.finditer(line[:cursor_offset]): + start = match.start() + 1 return start, line[:start] + line[cursor_offset:], line[start:cursor_offset] @edit_keys.on('') @@ -259,6 +273,10 @@ def delete_from_cursor_forward(cursor_offset, line): def titlecase_next_word(cursor_offset, line): return cursor_offset, line #TODO Not implemented + +delete_word_from_cursor_back_re = LazyReCompile(r'\b\w') + + @edit_keys.on('') @edit_keys.on('') @kills_behind @@ -266,9 +284,11 @@ def delete_word_from_cursor_back(cursor_offset, line): """Whatever my option-delete does in bash on my mac""" if not line: return cursor_offset, line, '' - starts = [m.start() for m in list(re.finditer(r'\b\w', line)) if m.start() < cursor_offset] - if starts: - return starts[-1], line[:starts[-1]] + line[cursor_offset:], line[starts[-1]:cursor_offset] - return cursor_offset, line, '' - - + start = None + for match in delete_word_from_cursor_back_re.finditer(line): + if match.start() < cursor_offset: + start = match.start() + if start is not None: + return start, line[:start] + line[cursor_offset:], line[start:cursor_offset] + else: + return cursor_offset, line, '' From 8a7e5d4ef23984b834aa70574ff520b81788d7c1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Feb 2015 23:31:21 +0100 Subject: [PATCH 0330/1650] More compiled regular expressions Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/preprocess.py | 11 ++++++++--- bpython/inspection.py | 15 ++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index 124a82a4b..4ee834438 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -1,8 +1,13 @@ """Tools for preparing code to be run in the REPL (removing blank lines, etc)""" -import re + +from bpython.lazyre import LazyReCompile #TODO specifically catch IndentationErrors instead of any syntax errors + +indent_empty_lines_re = LazyReCompile(r'\s*') + + def indent_empty_lines(s, compiler): """Indents blank lines that would otherwise cause early compilation @@ -16,8 +21,8 @@ def indent_empty_lines(s, compiler): for p_line, line, n_line in zip([''] + lines[:-1], lines, lines[1:] + ['']): if len(line) == 0: - p_indent = re.match(r'\s*', p_line).group() - n_indent = re.match(r'\s*', n_line).group() + p_indent = indent_empty_lines_re.match(p_line).group() + n_indent = indent_empty_lines_re.match(n_line).group() result_lines.append(min([p_indent, n_indent], key=len) + line) else: result_lines.append(line) diff --git a/bpython/inspection.py b/bpython/inspection.py index 0b0863bc6..406f56056 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -26,12 +26,12 @@ import inspect import keyword import pydoc -import re import types from pygments.token import Token from bpython._py3compat import PythonLexer, py3 +from bpython.lazyre import LazyReCompile try: collections.Callable @@ -45,7 +45,7 @@ has_instance_type = False if not py3: - _name = re.compile(r'[a-zA-Z_]\w*$') + _name = LazyReCompile(r'[a-zA-Z_]\w*$') class AttrCleaner(object): @@ -175,14 +175,16 @@ def fixlongargs(f, argspec): argspec[3] = values +getpydocspec_re = LazyReCompile(r'([a-zA-Z_][a-zA-Z0-9_]*?)\((.*?)\)') + + def getpydocspec(f, func): try: argspec = pydoc.getdoc(f) except NameError: return None - rx = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*?)\((.*?)\)') - s = rx.search(argspec) + s = getpydocspec_re.search(argspec) if s is None: return None @@ -272,9 +274,12 @@ def is_callable(obj): return callable(obj) +get_encoding_re = LazyReCompile(r'coding[:=]\s*([-\w.]+)') + + def get_encoding(obj): for line in inspect.findsource(obj)[0][:2]: - m = re.search(r'coding[:=]\s*([-\w.]+)', line) + m = get_encoding_re.search(line) if m: return m.group(1) return 'ascii' From 752d684fc204276c01e3efa5a7f7cbbc04953c22 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 3 Feb 2015 03:25:48 +0100 Subject: [PATCH 0331/1650] Less list comprehensions Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 26 ++++++++++++++++--------- bpython/curtsiesfrontend/replpainter.py | 3 ++- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 397ecf063..68c2980e1 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -907,7 +907,8 @@ def unhighlight_paren(self): def clear_current_block(self, remove_from_history=True): self.display_buffer = [] if remove_from_history: - [self.history.pop() for _ in self.buffer] + for _ in self.buffer: + self.history.pop() self.buffer = [] self.cursor_offset = 0 self.saved_indent = 0 @@ -923,7 +924,9 @@ def send_to_stdout(self, output): self.current_stdouterr_line += lines[0] if len(lines) > 1: self.display_lines.extend(paint.display_linize(self.current_stdouterr_line, self.width, blank_line=True)) - self.display_lines.extend(sum([paint.display_linize(line, self.width, blank_line=True) for line in lines[1:-1]], [])) + self.display_lines.extend(sum((paint.display_linize(line, self.width, + blank_line=True) + for line in lines[1:-1]), [])) self.current_stdouterr_line = lines[-1] logger.debug('display_lines: %r', self.display_lines) @@ -931,7 +934,9 @@ def send_to_stderr(self, error): lines = error.split('\n') if lines[-1]: self.current_stdouterr_line += lines[-1] - self.display_lines.extend(sum([paint.display_linize(line, self.width, blank_line=True) for line in lines[:-1]], [])) + self.display_lines.extend(sum((paint.display_linize(line, self.width, + blank_line=True) for + line in lines[:-1]), [])) def send_to_stdin(self, line): if line.endswith('\n'): @@ -1017,8 +1022,11 @@ def current_cursor_line(self): @property def current_suggestion(self): - matches = [e for e in self.rl_history.entries if e.startswith(self.current_line) and self.current_line] - return matches[-1][len(self.current_line):] if matches else '' + if self.current_line: + for entry in reversed(self.rl_history.entries): + if entry.startswith(self.current_line): + return entry[len(self.current_line):] + return '' @property def current_output_line(self): @@ -1349,8 +1357,8 @@ def reevaluate(self, insert_into_history=False): num_lines_onscreen = len(self.lines_for_display) - max(0, self.scroll_offset) display_lines_offscreen = self.display_lines[:len(self.display_lines) - num_lines_onscreen] old_display_lines_offscreen = old_display_lines[:len(self.display_lines) - num_lines_onscreen] - logger.debug('old_display_lines_offscreen %s', '|'.join([str(x) for x in old_display_lines_offscreen])) - logger.debug(' display_lines_offscreen %s', '|'.join([str(x) for x in display_lines_offscreen])) + logger.debug('old_display_lines_offscreen %s', '|'.join(str(x) for x in old_display_lines_offscreen)) + logger.debug(' display_lines_offscreen %s', '|'.join(str(x) for x in display_lines_offscreen)) if old_display_lines_offscreen[:len(display_lines_offscreen)] != display_lines_offscreen and not self.history_already_messed_up: #self.scroll_offset = self.scroll_offset + (len(old_display_lines)-len(self.display_lines)) self.inconsistent_history = True @@ -1361,8 +1369,8 @@ def reevaluate(self, insert_into_history=False): def getstdout(self): lines = self.lines_for_display + [self.current_line_formatted] - s = '\n'.join([x.s if isinstance(x, FmtStr) else x for x in lines] - ) if lines else '' + s = '\n'.join(x.s if isinstance(x, FmtStr) else x for x in lines) \ + if lines else '' return s def focus_on_subprocess(self, args): diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 44780163b..a3d5e5b3a 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + import logging from curtsies import fsarray, fmtstr @@ -175,7 +176,7 @@ def add_border(line): def paint_last_events(rows, columns, names, config): if not names: return fsarray([]) - width = min(max([len(name) for name in names] + [0]), columns-2) + width = min(max(len(name) for name in names), columns-2) output_lines = [] output_lines.append(config.left_top_corner+config.top_border*(width)+config.right_top_corner) for name in reversed(names[max(0, len(names)-(rows-2)):]): From 2f090fc1e645b580c1b5c9c7ac118eacb4305ac3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 3 Feb 2015 13:48:06 +0100 Subject: [PATCH 0332/1650] Drop GTK section Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/configuration-options.rst | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 30a696170..46483b81c 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -54,8 +54,8 @@ by the ID of the new paste (default: https://bpaste.net/show/$paste_id/). pastebin_removal_url ^^^^^^^^^^^^^^^^^^^^ -The url under which a paste can be removed. ``$removal_id`` will be replaced -by the removal ID of the paste (default: https://bpaste.net/remova/$removal_id/). +The url under which a paste can be removed. ``$removal_id`` will be replaced by +the removal ID of the paste (default: https://bpaste.net/remova/$removal_id/). .. versionadded:: 0.14 @@ -310,17 +310,6 @@ Default: False Trims lines starting with '>>> ' when set to True. -GTK ---- -This refers to the ``[gtk]`` section in your `$XDG_CONFIG_HOME/bpython/config` -file. - -font -^^^^ -Default: Monospace 10 - -The font to be used by the GTK version. - curtsies -------- This refers to the ``[curtsies]`` section in your config file. From b96ada1d9ef7203567e645ad752dbcfedb529fcc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 3 Feb 2015 15:36:35 +0100 Subject: [PATCH 0333/1650] Remove another list comprehension Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 68c2980e1..cee0d72fb 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -907,7 +907,7 @@ def unhighlight_paren(self): def clear_current_block(self, remove_from_history=True): self.display_buffer = [] if remove_from_history: - for _ in self.buffer: + for unused in self.buffer: self.history.pop() self.buffer = [] self.cursor_offset = 0 @@ -1489,7 +1489,8 @@ def request_refresh(): r.width = 50 r.height = 10 while True: - [_ for _ in importcompletion.find_iterator] + while not importcompletion.find_coroutine(): + pass r.dumb_print_output() r.dumb_input(refreshes) From 01cb7d001eb2472d81aec0038e1fe0c1e78748db Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 3 Feb 2015 15:36:53 +0100 Subject: [PATCH 0334/1650] Fix intendation Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/replpainter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index a3d5e5b3a..ef2d38195 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -166,8 +166,8 @@ def add_border(line): config.top_border * (width + 2) + config.right_top_corner) bottom_line = border_color(config.left_bottom_corner + - config.bottom_border * (width + 2) + - config.right_bottom_corner) + config.bottom_border * (width + 2) + + config.right_bottom_corner) output_lines = [top_line] + map(add_border, lines) + [bottom_line] r = fsarray(output_lines[:min(rows-1, len(output_lines)-1)] + output_lines[-1:]) From b85f0b8cb7ad8bd0880a0878f6ff28d38bd99a1b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 3 Feb 2015 23:17:24 -0500 Subject: [PATCH 0335/1650] test for issue #472 Appears to be causes by unhighlight paren repainting the wrong tokens --- bpython/test/test_curtsies_painting.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 93ffebd03..715cf707a 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -122,6 +122,7 @@ def refresh(self): def send_refreshes(self): while self.refresh_requests: self.repl.process_event(self.refresh_requests.pop()) + _, _ = self.repl.paint() def enter(self, line=None): """Enter a line of text, avoiding autocompletion windows @@ -504,3 +505,26 @@ def test_unhighlight_paren_bugs(self): screen = fsarray([cyan(u">>> ")+yellow('('), green(u"... ")+yellow(')')+bold(cyan(" "))]) self.assert_paint(screen, (1, 6)) + + def send_key(self, key): + self.repl.process_event(u'' if key == ' ' else key) + self.repl.paint() # has some side effects we need to be wary of + + def test_472(self): + [self.send_key(c) for c in "(1, 2, 3)"] + with output_to_repl(self.repl): + self.send_key('\n') + self.send_refreshes() + self.send_key('') + self.repl.paint() + [self.send_key('') for _ in range(4)] + self.send_key('') + self.send_key('4') + self.repl.on_enter() + self.send_refreshes() + screen = [">>> (1, 2, 3)", + '(1, 2, 3)', + '>>> (1, 4, 3)', + '(1, 4, 3)', + '>>> '] + self.assert_paint_ignoring_formatting(screen, (4, 4)) From bf9550c33d635f3a9025f6afd77223917a6d847f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 14:20:45 +0100 Subject: [PATCH 0336/1650] Only use English translations for tests Signed-off-by: Sebastian Ramacher --- bpython/test/__init__.py | 12 ++++++++++++ bpython/test/test_curtsies_painting.py | 3 ++- bpython/test/test_curtsies_repl.py | 15 ++++++++------- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index e69de29bb..16530c33a 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -0,0 +1,12 @@ +try: + import unittest2 as unittest +except ImportError: + import unittest + +from bpython.translations import init + +class FixLanguageTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + init(languages=['en']) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 715cf707a..971092e60 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -18,13 +18,14 @@ from bpython.curtsiesfrontend import replpainter from bpython.repl import History from bpython.curtsiesfrontend.repl import INCONSISTENT_HISTORY_MSG, CONTIGUITY_BROKEN_MSG +from bpython.test import FixLanguageTestCase as TestCase def setup_config(): config_struct = config.Struct() config.loadini(config_struct, os.devnull) return config_struct -class TestCurtsiesPainting(FormatStringTest): +class TestCurtsiesPainting(FormatStringTest, TestCase): def setUp(self): self.repl = Repl(config=setup_config()) self.repl.rl_history = History() # clear history diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 4cfac5f72..e1625dcfc 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -18,6 +18,7 @@ from bpython import config from bpython import args from bpython._py3compat import py3 +from bpython.test import FixLanguageTestCase as TestCase def setup_config(conf): config_struct = config.Struct() @@ -29,7 +30,7 @@ def setup_config(conf): return config_struct -class TestCurtsiesRepl(unittest.TestCase): +class TestCurtsiesRepl(TestCase): def setUp(self): self.repl = create_repl() @@ -88,7 +89,7 @@ def mock_next(obj, return_value): else: obj.next.return_value = return_value -class TestCurtsiesReplTab(unittest.TestCase): +class TestCurtsiesReplTab(TestCase): def setUp(self): self.repl = create_repl() @@ -153,7 +154,7 @@ def test_tab_completes_common_sequence(self): self.repl.matches_iter.substitute_cseq.assert_called_once_with() -class TestCurtsiesReplFilenameCompletion(unittest.TestCase): +class TestCurtsiesReplFilenameCompletion(TestCase): def setUp(self): self.repl = create_repl() @@ -213,7 +214,7 @@ def create_repl(**kwargs): repl.height = 20 return repl -class TestFutureImports(unittest.TestCase): +class TestFutureImports(TestCase): def test_repl(self): repl = create_repl() @@ -236,7 +237,7 @@ def test_interactive(self): self.assertEqual(out.getvalue(), '0.5\n0.5\n') -class TestPredictedIndent(unittest.TestCase): +class TestPredictedIndent(TestCase): def setUp(self): self.repl = create_repl() @@ -253,7 +254,7 @@ def test_complex(self): self.assertEqual(self.repl.predicted_indent('reduce(asdfasdf,'), 7) -class TestCurtsiesReevaluate(unittest.TestCase): +class TestCurtsiesReevaluate(TestCase): def setUp(self): self.repl = create_repl() @@ -264,7 +265,7 @@ def test_variable_is_cleared(self): self.repl.undo() self.assertNotIn('b', self.repl.interp.locals) -class TestCurtsiesPagerText(unittest.TestCase): +class TestCurtsiesPagerText(TestCase): def setUp(self): self.repl = create_repl() From dd29ad52b7d5dd7d43c45e65da7c9dd93c826abc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 14:21:04 +0100 Subject: [PATCH 0337/1650] Move the call to translations.init to main Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 3 +++ bpython/curtsiesfrontend/repl.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index d9189b843..ffa83645b 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -13,6 +13,7 @@ from bpython.curtsiesfrontend.repl import Repl from bpython.curtsiesfrontend.coderunner import SystemExitFromCodeGreenlet from bpython import args as bpargs +from bpython import translations from bpython.translations import _ from bpython.importcompletion import find_iterator from bpython.curtsiesfrontend import events as bpythonevents @@ -25,6 +26,8 @@ def main(args=None, locals_=None, banner=None): + translations.init() + config, options, exec_args = bpargs.parse(args, ( 'curtsies options', None, [ Option('--log', '-L', action='count', diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index cee0d72fb..22a3c5fe8 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -29,7 +29,6 @@ from bpython.config import Struct, loadini, default_config_path from bpython.formatter import BPythonFormatter from bpython import autocomplete, importcompletion -from bpython import translations; translations.init() from bpython.translations import _ from bpython._py3compat import py3 from bpython.pager import get_pager_command From 16b091affbf04c3cafba2aaa8850fde9e9c7c407 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 15:46:04 +0100 Subject: [PATCH 0338/1650] Use unicode internally Only encode when necessary. Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 22a3c5fe8..9e68f32c6 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -14,7 +14,7 @@ import unicodedata from pygments import format -from bpython._py3compat import PythonLexer, cast_bytes +from bpython._py3compat import PythonLexer from pygments.formatters import TerminalFormatter import blessings @@ -26,7 +26,8 @@ import bpython from bpython.repl import Repl as BpythonRepl, SourceNotFound -from bpython.config import Struct, loadini, default_config_path +from bpython.config import Struct, loadini, default_config_path, \ + getpreferredencoding from bpython.formatter import BPythonFormatter from bpython import autocomplete, importcompletion from bpython.translations import _ @@ -1395,10 +1396,10 @@ def focus_on_subprocess(self, args): def pager(self, text): """Runs an external pager on text - text must be a bytestring, ie not yet encoded""" + text must be a unicode""" command = get_pager_command() with tempfile.NamedTemporaryFile() as tmp: - tmp.write(text) + tmp.write(text.encode(getpreferredencoding())) tmp.flush() self.focus_on_subprocess(command + [tmp.name]) @@ -1409,14 +1410,12 @@ def show_source(self): self.status_bar.message(str(e)) else: if self.config.highlight_show_source: - source = cast_bytes(format(PythonLexer().get_tokens(source), - TerminalFormatter())) - else: - source = cast_bytes(source) + source = format(PythonLexer().get_tokens(source), + TerminalFormatter()) self.pager(source) def help_text(self): - return cast_bytes(self.version_help_text() + '\n' + self.key_help_text()) + return self.version_help_text() + '\n' + self.key_help_text() def version_help_text(self): return (('bpython-curtsies version %s' % bpython.__version__) + ' ' + From 42a253af14b795bfa3f909c3259bb8a8a2d910e1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 15:46:21 +0100 Subject: [PATCH 0339/1650] Add can_encode Signed-off-by: Sebastian Ramacher --- bpython/config.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 455847283..8b481268f 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -21,15 +21,16 @@ def getpreferredencoding(): return locale.getpreferredencoding() or sys.getdefaultencoding() -def supports_box_chars(): - """Check if the encoding suppors Unicode box characters.""" +def can_encode(c): try: - for c in (u'│', u'│', u'─', u'─', u'└', u'┘', u'┌', u'┐'): - c.encode(getpreferredencoding()) + c.encode(getpreferredencoding()) return True except UnicodeEncodeError: return False +def supports_box_chars(): + """Check if the encoding suppors Unicode box characters.""" + return all(map( can_encode, u'│─└┘┌┐')) def get_config_home(): """Returns the base directory for bpython's configuration files.""" From 7bcaf4c02c6c5d38a06a59d79ff11b18b91245af Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 15:46:51 +0100 Subject: [PATCH 0340/1650] Make more tests pass in a non utf-8 locale Signed-off-by: Sebastian Ramacher --- bpython/test/test_curtsies_painting.py | 35 ++++++++++++++++++-------- bpython/test/test_curtsies_repl.py | 17 +++++++++---- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 971092e60..96ec22ba2 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -74,11 +74,18 @@ def test_completion(self): self.repl.height, self.repl.width = (5, 32) self.repl.current_line = 'se' self.cursor_offset = 2 - screen = [u'>>> se', - u'┌───────────────────────┐', - u'│ set( setattr( │', - u'└───────────────────────┘', - u'Welcome to bpython! Press f'] + if config.supports_box_chars(): + screen = [u'>>> se', + u'┌───────────────────────┐', + u'│ set( setattr( │', + u'└───────────────────────┘', + u'Welcome to bpython! Press f'] + else: + screen = [u'>>> se', + u'+-----------------------+', + u'| set( setattr( |', + u'+-----------------------+', + u'Welcome to bpython! Press f'] self.assert_paint_ignoring_formatting(screen, (0, 4)) def test_argspec(self): @@ -100,11 +107,19 @@ def test_formatted_docstring(self): self.assertFSArraysEqualIgnoringFormatting(actual, expected) def test_paint_lasts_events(self): - actual = replpainter.paint_last_events(4, 100, ['a', 'b', 'c'], config=setup_config()) - expected = fsarray(["┌─┐", - "│c│", - "│b│", - "└─┘"]) + actual = replpainter.paint_last_events(4, 100, ['a', 'b', 'c'], + config=setup_config()) + if config.supports_box_chars(): + expected = fsarray(["┌─┐", + "│c│", + "│b│", + "└─┘"]) + else: + expected = fsarray(["+-+", + "|c|", + "|b|", + "+-+"]) + self.assertFSArraysEqualIgnoringFormatting(actual, expected) @contextmanager diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index e1625dcfc..be7e22518 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -1,4 +1,6 @@ # coding: utf8 +from __future__ import unicode_literals + import code from contextlib import contextmanager from mock import Mock, patch, MagicMock @@ -53,13 +55,14 @@ def test_code_finished_will_parse(self): self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) def test_external_communication(self): - self.assertEqual(type(self.repl.help_text()), type(b'')) self.repl.send_current_block_to_external_editor() self.repl.send_session_to_external_editor() + @unittest.skipIf(not all(map(config.can_encode, 'å∂߃')), + 'Charset can not encode characters') def test_external_communication_encoding(self): with captured_output(): - self.repl.display_lines.append(u'>>> "åß∂ƒ"') + self.repl.display_lines.append('>>> "åß∂ƒ"') self.repl.send_session_to_external_editor() def test_get_last_word(self): @@ -269,19 +272,23 @@ class TestCurtsiesPagerText(TestCase): def setUp(self): self.repl = create_repl() - self.repl.pager = self.assert_pager_gets_bytes + self.repl.pager = self.assert_pager_gets_unicode - def assert_pager_gets_bytes(self, text): - self.assertIsInstance(text, type(b'')) + def assert_pager_gets_unicode(self, text): + self.assertIsInstance(text, type('')) def test_help(self): self.repl.pager(self.repl.help_text()) + @unittest.skipIf(not all(map(config.can_encode, 'å∂߃')), + 'Charset can not encode characters') def test_show_source_not_formatted(self): self.repl.config.highlight_show_source = False self.repl.get_source_of_current_name = lambda: 'source code å∂߃åß∂ƒ' self.repl.show_source() + @unittest.skipIf(not all(map(config.can_encode, 'å∂߃')), + 'Charset can not encode characters') def test_show_source_formatted(self): self.repl.config.highlight_show_source = True self.repl.get_source_of_current_name = lambda: 'source code å∂߃åß∂ƒ' From d9a4f3ea2c816bb3a39bdaf48b04c72adf2fbb90 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 15:46:59 +0100 Subject: [PATCH 0341/1650] Remove unused functions Signed-off-by: Sebastian Ramacher --- bpython/_py3compat.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index 3894a6a28..e784d8851 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -43,15 +43,3 @@ from pygments.lexers import Python3Lexer as PythonLexer else: from pygments.lexers import PythonLexer - - -def cast_unicode(s, encoding=locale.getpreferredencoding()): - if isinstance(s, bytes): - return s.decode(encoding) - return s - - -def cast_bytes(s, encoding=locale.getpreferredencoding()): - if not isinstance(s, bytes): - return s.encode(encoding) - return s From 301a3540b550009c69098680b2bf21cc9cc2aa1c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 17:21:18 +0100 Subject: [PATCH 0342/1650] Use print function Signed-off-by: Sebastian Ramacher --- bpdb/__init__.py | 30 ++++++++++++++++-------------- bpdb/debugger.py | 9 +++++---- bpython/args.py | 10 +++++----- bpython/urwid.py | 6 +++--- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index ac088796d..1db4d493a 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -21,6 +21,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from __future__ import print_function + import os import sys import traceback @@ -65,16 +67,16 @@ def main(): help='Print version and exit.') options, args = parser.parse_args(sys.argv) if options.version: - print 'bpdb on top of bpython version', __version__, - print 'on top of Python', sys.version.split()[0] - print ('(C) 2008-2013 Bob Farrell, Andreas Stuehrk et al. ' - 'See AUTHORS for detail.') + print('bpdb on top of bpython version', __version__, end="") + print('on top of Python', sys.version.split()[0]) + print('(C) 2008-2013 Bob Farrell, Andreas Stuehrk et al. ' + 'See AUTHORS for detail.') return 0 # The following code is baed on Python's pdb.py. mainpyfile = args[1] if not os.path.exists(mainpyfile): - print 'Error:', mainpyfile, 'does not exist' + print('Error:', mainpyfile, 'does not exist') return 1 # Hide bpdb from argument list. @@ -89,20 +91,20 @@ def main(): pdb._runscript(mainpyfile) if pdb._user_requested_quit: break - print "The program finished and will be restarted" + print("The program finished and will be restarted") except Restart: - print "Restarting", mainpyfile, "with arguments:" - print "\t" + " ".join(sys.argv[1:]) + print("Restarting", mainpyfile, "with arguments:") + print("\t" + " ".join(sys.argv[1:])) except SystemExit: # In most cases SystemExit does not warrant a post-mortem session. - print "The program exited via sys.exit(). Exit status: ", - print sys.exc_info()[1] + print("The program exited via sys.exit(). Exit status: ",) + print(sys.exc_info()[1]) except: traceback.print_exc() - print "Uncaught exception. Entering post mortem debugging" - print "Running 'cont' or 'step' will restart the program" + print("Uncaught exception. Entering post mortem debugging") + print("Running 'cont' or 'step' will restart the program") t = sys.exc_info()[2] pdb.interaction(None, t) - print "Post mortem debugger finished. The " + mainpyfile + \ - " will be restarted" + print("Post mortem debugger finished. The " + mainpyfile + \ + " will be restarted") diff --git a/bpdb/debugger.py b/bpdb/debugger.py index fc3908bb7..6f66dd15b 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -20,6 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from __future__ import print_function import pdb import bpython @@ -46,10 +47,10 @@ def do_Bpython(self, arg): def help_Bpython(self): - print "B(python)" - print - print ("Invoke the bpython interpreter for this stack frame. To exit " - "bpython and return to a standard pdb press Ctrl-d") + print("B(python)") + print("") + print("Invoke the bpython interpreter for this stack frame. To exit " + "bpython and return to a standard pdb press Ctrl-d") ### shortcuts diff --git a/bpython/args.py b/bpython/args.py index 0e97985ad..d98820bfd 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -2,7 +2,7 @@ Module to handle command line argument parsing, for all front-ends. """ -from __future__ import with_statement +from __future__ import with_statement, print_function import os import sys import code @@ -86,10 +86,10 @@ def parse(args, extras=None, ignore_stdin=False): os.execv(sys.executable, [sys.executable] + args) if options.version: - print 'bpython version', __version__, - print 'on top of Python', sys.version.split()[0] - print ('(C) 2008-2014 Bob Farrell, Andreas Stuehrk et al. ' - 'See AUTHORS for detail.') + print('bpython version', __version__, eof="") + print('on top of Python', sys.version.split()[0]) + print('(C) 2008-2015 Bob Farrell, Andreas Stuehrk et al. ' + 'See AUTHORS for detail.') raise SystemExit if not ignore_stdin and not (sys.stdin.isatty() and sys.stdout.isatty()): diff --git a/bpython/urwid.py b/bpython/urwid.py index 0f249f696..6f72180ca 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -33,7 +33,7 @@ """ -from __future__ import absolute_import, with_statement, division +from __future__ import absolute_import, with_statement, division, print_function import sys import os @@ -144,7 +144,7 @@ def wrapper(*args, **kwargs): # This is the same as in urwid. # We are obviously not supposed to ever hit this. import sys - print sys.exc_info() + print(sys.exc_info()) self._exc_info = sys.exc_info() self.reactor.crash() return wrapper @@ -1141,7 +1141,7 @@ def main(args=None, locals_=None, banner=None): from twisted.application import reactors # Stolen from twisted.application.app (twistd). for r in reactors.getReactorTypes(): - print ' %-4s\t%s' % (r.shortName, r.description) + print(' %-4s\t%s' % (r.shortName, r.description)) except ImportError: sys.stderr.write('No reactors are available. Please install ' 'twisted for reactor support.\n') From ee2ab5781d6183cdb7f23bf25a0171c5b264e7f7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 17:31:19 +0100 Subject: [PATCH 0343/1650] Use range everywhere Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 10 +++++----- bpython/history.py | 4 ++-- bpython/keys.py | 4 ++-- bpython/test/test_crashers.py | 2 +- bpython/test/test_manual_readline.py | 8 ++++---- bpython/urwid.py | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 50be05b8d..59089a68f 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -444,7 +444,7 @@ def clear_wrapped_lines(self): # curses does not handle this on its own. Sad. height, width = self.scr.getmaxyx() max_y = min(self.iy + (self.ix + len(self.s)) // width + 1, height) - for y in xrange(self.iy + 1, max_y): + for y in range(self.iy + 1, max_y): self.scr.move(y, 0) self.scr.clrtoeol() @@ -660,7 +660,7 @@ def get_line(self): self.iy, self.ix = self.scr.getyx() if not self.paste_mode: - for _ in xrange(self.next_indentation()): + for _ in range(self.next_indentation()): self.p_key('\t') self.cpos = 0 @@ -1156,7 +1156,7 @@ def reprint_line(self, lineno, tokens): real_lineno = self.iy height, width = self.scr.getmaxyx() - for i in xrange(lineno, len(self.buffer)): + for i in range(lineno, len(self.buffer)): string = self.buffer[i] # 4 = length of prompt length = len(string.encode(getpreferredencoding())) + 4 @@ -1223,7 +1223,7 @@ def reevaluate(self): self.scr.refresh() if self.buffer: - for _ in xrange(indent): + for _ in range(indent): self.tab() self.evaluating = False @@ -1517,7 +1517,7 @@ def send_current_line_to_editor(self): self.scr.refresh() if self.buffer: - for _ in xrange(indent): + for _ in range(indent): self.tab() self.print_line(self.s) diff --git a/bpython/history.py b/bpython/history.py index 80952a9d8..15335e0c7 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -127,7 +127,7 @@ def forward(self, start=True, search=False, target=None, def find_match_forward(self, search_term, include_current=False): add = 0 if include_current else 1 end = max(0, self.index - (1 - add)) - for idx in xrange(end): + for idx in range(end): val = self.entries_by_index[end - 1 - idx] if val.startswith(search_term): return idx + (0 if include_current else 1) @@ -136,7 +136,7 @@ def find_match_forward(self, search_term, include_current=False): def find_partial_match_forward(self, search_term, include_current=False): add = 0 if include_current else 1 end = max(0, self.index - (1 - add)) - for idx in xrange(end): + for idx in range(end): val = self.entries_by_index[end - 1 - idx] if search_term in val: return idx + add diff --git a/bpython/keys.py b/bpython/keys.py index d401c5d6e..0553fe56f 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -66,8 +66,8 @@ def __setitem__(self, key, value): cli_key_dispatch['C-_'] = (chr(31), '^_') # fill dispatch with function keys -for x in xrange(1, 13): +for x in range(1, 13): cli_key_dispatch['F%s' % str(x)] = ('KEY_F(%s)' % str(x),) -for x in xrange(1, 13): +for x in range(1, 13): urwid_key_dispatch['F%s' % str(x)] = 'f%s' % str(x) diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 9ed275e08..037f8954e 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -53,7 +53,7 @@ def run_bpython(self, input): result = Deferred() class Protocol(ProcessProtocol): - STATES = (SEND_INPUT, COLLECT) = xrange(2) + STATES = (SEND_INPUT, COLLECT) = range(2) def __init__(self): self.data = "" diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 9a7cf6cef..91a787eaf 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -25,7 +25,7 @@ def test_left_arrow_at_zero(self): self.assertEquals(expected, result) def test_left_arrow_at_non_zero(self): - for i in xrange(1, len(self._line)): + for i in range(1, len(self._line)): expected = (i-1, self._line) result = left_arrow(i, self._line) self.assertEqual(expected, result) @@ -37,20 +37,20 @@ def test_right_arrow_at_end(self): self.assertEquals(expected, result) def test_right_arrow_at_non_end(self): - for i in xrange(len(self._line) - 1): + for i in range(len(self._line) - 1): expected = (i + 1, self._line) result = right_arrow(i, self._line) self.assertEquals(expected, result) def test_beginning_of_line(self): expected = (0, self._line) - for i in xrange(len(self._line)): + for i in range(len(self._line)): result = beginning_of_line(i, self._line) self.assertEquals(expected, result) def test_end_of_line(self): expected = (len(self._line), self._line) - for i in xrange(len(self._line)): + for i in range(len(self._line)): result = end_of_line(i, self._line) self.assertEquals(expected, result) diff --git a/bpython/urwid.py b/bpython/urwid.py index 6f72180ca..e76396224 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -893,7 +893,7 @@ def reevaluate(self): self.scr.refresh() if self.buffer: - for unused in xrange(indent): + for unused in range(indent): self.tab() self.evaluating = False From 02d1260e0011b40142aad36efa71270c5f9b0c59 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 17:32:24 +0100 Subject: [PATCH 0344/1650] Python 3 compatible ConfigParser import --- bpython/config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index 8b481268f..7f87655a6 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -4,12 +4,16 @@ import os import sys import locale -from ConfigParser import ConfigParser from itertools import chain from bpython.keys import cli_key_dispatch as key_dispatch from bpython.autocomplete import SIMPLE as default_completion import bpython.autocomplete +try: + from configparser import ConfigParser +except ImportError: + from ConfigParser import ConfigParser + class Struct(object): """Simple class for instantiating objects we can add arbitrary attributes From f95b6b4b674de3b933af06d72b77cd0a28352b40 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 17:32:50 +0100 Subject: [PATCH 0345/1650] Use methods without iter Signed-off-by: Sebastian Ramacher --- bpython/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 7f87655a6..4c2a15874 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -48,11 +48,11 @@ def default_config_path(): def fill_config_with_default_values(config, default_values): - for section in default_values.iterkeys(): + for section in default_values: if not config.has_section(section): config.add_section(section) - for (opt, val) in default_values[section].iteritems(): + for (opt, val) in default_values[section].items(): if not config.has_option(section, opt): config.set(section, opt, str(val)) @@ -130,7 +130,7 @@ def loadini(struct, configfile): }} default_keys_to_commands = dict((value, key) for (key, value) - in defaults['keyboard'].iteritems()) + in defaults['keyboard'].items()) fill_config_with_default_values(config, defaults) if not config.read(config_path): @@ -307,6 +307,6 @@ def load_theme(struct, path, colors, default_colors): colors[k] = theme.get('interface', k) # Check against default theme to see if all values are defined - for k, v in default_colors.iteritems(): + for k, v in default_colors.items(): if k not in colors: colors[k] = v From eeb2b8953ea1ae9477e46e33685ac0ec0f22461b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 17:33:06 +0100 Subject: [PATCH 0346/1650] Python compatible url* imports Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 31bc1894b..9c9c45a9c 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -40,8 +40,6 @@ from itertools import takewhile from locale import getpreferredencoding from string import Template -from urllib import quote as urlquote -from urlparse import urlparse, urljoin from pygments.token import Token @@ -54,6 +52,15 @@ import bpython.autocomplete as autocomplete +try: + from urllib.parse import quote as urlquote + from urllib.parse import urljoin + from urllib.parse import urlparse +except ImportError: + from urllib import quote as urlquote + from urlparse import urlparse, urljoin + + class RuntimeTimer(object): def __init__(self): self.reset_timer() From 2afe1d0ebc36daed963b1c8184ca1aae8445797d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 17:33:23 +0100 Subject: [PATCH 0347/1650] Python 3 compatible ifilter --- bpython/importcompletion.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index d7bb1bf96..cd3c9de5a 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -29,7 +29,11 @@ import sys import warnings from warnings import catch_warnings -from itertools import ifilter + +if py3: + ifilter = filter +else: + from itertools import ifilter if sys.version_info[0] == 3 and sys.version_info[1] >= 3: import importlib.machinery From 7e0653bd4b295b4f855e55910ac0864922c57a32 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 17:44:46 +0100 Subject: [PATCH 0348/1650] Use io.BytesIO instead of StringsIO.StringsIO Signed-off-by: Sebastian Ramacher --- bpython/test/test_curtsies_repl.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index be7e22518..40958dc5e 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -5,7 +5,6 @@ from contextlib import contextmanager from mock import Mock, patch, MagicMock import os -from StringIO import StringIO import sys import tempfile @@ -14,6 +13,11 @@ except ImportError: import unittest +try: + from io import BytesIO +except ImportError: + from StringIO import StringIO as BytesIO + from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter from bpython import autocomplete @@ -201,7 +205,7 @@ def test_list_win_not_visible_and_match_selected_if_one_option(self): @contextmanager # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy def captured_output(): - new_out, new_err = StringIO(), StringIO() + new_out, new_err = BytesIO(), BytesIO() old_out, old_err = sys.stdout, sys.stderr try: sys.stdout, sys.stderr = new_out, new_err From 331c9fd06b62642208863d7494df51c8f935d9a8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 17:54:33 +0100 Subject: [PATCH 0349/1650] Python 3 compatible __builtin__ import --- bpython/autocomplete.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 4f6fafbb1..c0c93d8c5 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -21,7 +21,6 @@ # THE SOFTWARE. # -import __builtin__ import __main__ import abc import keyword @@ -36,6 +35,11 @@ from bpython._py3compat import py3 from bpython.lazyre import LazyReCompile +try: + import builtins +except ImportError: + import __builtin__ as builtins + # Autocomplete modes SIMPLE = 'simple' @@ -286,7 +290,7 @@ def matches(self, cursor_offset, line, **kwargs): for word in keyword.kwlist: if method_match(word, n, text): matches.add(word) - for nspace in [__builtin__.__dict__, locals_]: + for nspace in [builtins.__dict__, locals_]: for word, val in nspace.items(): if method_match(word, len(text), text) and word != "__builtins__": matches.add(_callable_postfix(val, word)) From 1ef719c6308006b8b7233a6ad9a5940fcaa8c9f0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 17:54:54 +0100 Subject: [PATCH 0350/1650] Use methods without iter Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/interpreter.py | 2 +- bpython/formatter.py | 2 +- bpython/repl.py | 2 +- bpython/urwid.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index c8e6d150c..c1d341bb0 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -45,7 +45,7 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} - for k, v in color_scheme.iteritems(): + for k, v in color_scheme.items(): self.f_strings[k] = '\x01%s' % (v,) Formatter.__init__(self, **options) diff --git a/bpython/formatter.py b/bpython/formatter.py index 8586102aa..765a15a85 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -85,7 +85,7 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} - for k, v in theme_map.iteritems(): + for k, v in theme_map.items(): self.f_strings[k] = '\x01%s' % (color_scheme[v],) if k is Parenthesis: # FIXME: Find a way to make this the inverse of the current diff --git a/bpython/repl.py b/bpython/repl.py index 9c9c45a9c..7458f420a 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -948,7 +948,7 @@ def tokenize(self, s, newline=False): else: stack.append((line, len(line_tokens) - 1, line_tokens, value)) - elif value in parens.itervalues(): + elif value in parens.values(): saved_stack = list(stack) try: while True: diff --git a/bpython/urwid.py b/bpython/urwid.py index e76396224..e521393b9 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1150,7 +1150,7 @@ def main(args=None, locals_=None, banner=None): palette = [ (name, COLORMAP[color.lower()], 'default', 'bold' if color.isupper() else 'default') - for name, color in config.color_scheme.iteritems()] + for name, color in config.color_scheme.items()] palette.extend([ ('bold ' + name, color + ',bold', background, monochrome) for name, color, background, monochrome in palette]) From 1da77943476c299b56046855f4f6761a81f296e3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 18:06:11 +0100 Subject: [PATCH 0351/1650] Some Python 3 compatibility Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/replpainter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index ef2d38195..89ca29d58 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import logging +import itertools from curtsies import fsarray, fmtstr from curtsies.formatstring import linesplit @@ -169,7 +170,8 @@ def add_border(line): config.bottom_border * (width + 2) + config.right_bottom_corner) - output_lines = [top_line] + map(add_border, lines) + [bottom_line] + output_lines = list(itertools.chain((top_line, ), map(add_border, lines), + (bottom_line, ))) r = fsarray(output_lines[:min(rows-1, len(output_lines)-1)] + output_lines[-1:]) return r From 5e8d5828fe3fc81134758f0ff5ad4d3c8244f53a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 18:06:23 +0100 Subject: [PATCH 0352/1650] Use next(iter) instead of iter.next() Signed-off-by: Sebastian Ramacher --- bpython/line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/line.py b/bpython/line.py index a565d50b9..0a2ca045d 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -97,7 +97,7 @@ def current_object_attribute(cursor_offset, line): if match is None: return None start, end, word = match matches = current_object_attribute_re.finditer(word) - matches.next() + next(matches) for m in matches: if m.start(1) + start <= cursor_offset and m.end(1) + start >= cursor_offset: return m.start(1) + start, m.end(1) + start, m.group(1) From 4e477f8a429d6ffa2f30c6c452e6fa63e9eb6935 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 18:13:39 +0100 Subject: [PATCH 0353/1650] Add __bool__ and __next__ Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 7458f420a..f9966ffff 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -199,6 +199,9 @@ def __nonzero__(self): """MatchesIterator is False when word hasn't been replaced yet""" return self.index != -1 + def __bool__(self): + return self.__nonzero__() + @property def candidate_selected(self): """True when word selected/replaced, False when word hasn't been replaced yet""" @@ -212,10 +215,13 @@ def current(self): raise ValueError('No current match.') return self.matches[self.index] - def next(self): + def __next__(self): self.index = (self.index + 1) % len(self.matches) return self.matches[self.index] + def next(self): + return self.__next__() + def previous(self): if self.index <= 0: self.index = len(self.matches) From b642758382b09e87e6ad6e08925778a5186a663b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 18:30:39 +0100 Subject: [PATCH 0354/1650] Unbreak 2to3 Signed-off-by: Sebastian Ramacher --- bpython/importcompletion.py | 7 +------ bpython/repl.py | 10 +++++++--- bpython/test/test_repl.py | 16 ++++++++-------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index cd3c9de5a..8c8cfe5f5 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -30,11 +30,6 @@ import warnings from warnings import catch_warnings -if py3: - ifilter = filter -else: - from itertools import ifilter - if sys.version_info[0] == 3 and sys.version_info[1] >= 3: import importlib.machinery SUFFIXES = importlib.machinery.all_suffixes() @@ -88,7 +83,7 @@ def attr_matches(cw, prefix='', only_modules=False): matches = ('%s.%s' % (module_part, m) for m in matches) generator = (try_decode_module(match, 'ascii') for match in matches) - return set(ifilter(lambda x: x is not None, generator)) + return set(filter(lambda x: x is not None, generator)) def module_attr_matches(name): diff --git a/bpython/repl.py b/bpython/repl.py index f9966ffff..c7ecb2453 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -200,7 +200,7 @@ def __nonzero__(self): return self.index != -1 def __bool__(self): - return self.__nonzero__() + return self.index != -1 @property def candidate_selected(self): @@ -215,12 +215,16 @@ def current(self): raise ValueError('No current match.') return self.matches[self.index] - def __next__(self): + def _next_impl(self): + """Keep this around until we drop 2to3.""" self.index = (self.index + 1) % len(self.matches) return self.matches[self.index] def next(self): - return self.__next__() + return self._next_impl() + + def __next__(self): + return self._next_impl() def previous(self): if self.index <= 0: diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 263fd08a1..2a0545569 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -52,14 +52,14 @@ def setUp(self): self.matches_iterator.matches = self.matches def test_next(self): - self.assertEqual(self.matches_iterator.next(), self.matches[0]) + self.assertEqual(next(self.matches_iterator), self.matches[0]) for x in range(len(self.matches) - 1): - self.matches_iterator.next() + next(self.matches_iterator) - self.assertEqual(self.matches_iterator.next(), self.matches[0]) - self.assertEqual(self.matches_iterator.next(), self. matches[1]) - self.assertNotEqual(self.matches_iterator.next(), self.matches[1]) + self.assertEqual(next(self.matches_iterator), self.matches[0]) + self.assertEqual(next(self.matches_iterator), self. matches[1]) + self.assertNotEqual(next(self.matches_iterator), self.matches[1]) def test_previous(self): self.assertEqual(self.matches_iterator.previous(), self.matches[2]) @@ -76,7 +76,7 @@ def test_nonzero(self): then True once we active a match. """ self.assertFalse(self.matches_iterator) - self.matches_iterator.next() + next(self.matches_iterator) self.assertTrue(self.matches_iterator) def test_iter(self): @@ -85,7 +85,7 @@ def test_iter(self): def test_current(self): self.assertRaises(ValueError, self.matches_iterator.current) - self.matches_iterator.next() + next(self.matches_iterator) self.assertEqual(self.matches_iterator.current(), self.matches[0]) def test_update(self): @@ -110,7 +110,7 @@ def test_cur_line(self): self.assertRaises(ValueError, self.matches_iterator.cur_line) - self.assertEqual(self.matches_iterator.next(), self.matches[0]) + self.assertEqual(next(self.matches_iterator), self.matches[0]) self.assertEqual(self.matches_iterator.cur_line(), (len(self.matches[0]), self.matches[0])) From 0fe93e5c1e02553f522bd9d644978f629368f1c7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 18:50:03 +0100 Subject: [PATCH 0355/1650] Use six Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 2 +- setup.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index 219886e34..e59608a5a 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -6,7 +6,7 @@ pip install setuptools if [[ $RUN == nosetests ]]; then # core dependencies - pip install pygments requests 'curtsies >=0.1.17,<0.2.0' greenlet + pip install pygments requests 'curtsies >=0.1.17,<0.2.0' greenlet six # filewatch specific dependencies pip install watchdog # jedi specific dependencies diff --git a/setup.py b/setup.py index f5ec49a1a..054bcaf9d 100755 --- a/setup.py +++ b/setup.py @@ -169,7 +169,8 @@ def initialize_options(self): 'pygments', 'requests', 'curtsies >=0.1.17, <0.2.0', - 'greenlet' + 'greenlet', + 'six' ] extras_require = { From 13092bcaa58570704b478ec99c9aeaf0708ddae2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 18:57:49 +0100 Subject: [PATCH 0356/1650] Use six.moves.range and six.moves.builtins Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 6 +----- bpython/cli.py | 1 + bpython/curtsiesfrontend/repl.py | 2 ++ bpython/curtsiesfrontend/replpainter.py | 1 + bpython/history.py | 1 + bpython/keys.py | 2 ++ bpython/urwid.py | 1 + 7 files changed, 9 insertions(+), 5 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index c0c93d8c5..84d582929 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -26,6 +26,7 @@ import keyword import os import rlcompleter +from six.moves import range, builtins from glob import glob @@ -35,11 +36,6 @@ from bpython._py3compat import py3 from bpython.lazyre import LazyReCompile -try: - import builtins -except ImportError: - import __builtin__ as builtins - # Autocomplete modes SIMPLE = 'simple' diff --git a/bpython/cli.py b/bpython/cli.py index 59089a68f..e00148222 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -60,6 +60,7 @@ import locale from types import ModuleType +from six.moves import range # These are used for syntax highlighting from pygments import format diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9e68f32c6..d12722b37 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import contextlib import errno import functools @@ -12,6 +13,7 @@ import threading import time import unicodedata +from six.moves import range from pygments import format from bpython._py3compat import PythonLexer diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 89ca29d58..bb94c9bab 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -2,6 +2,7 @@ import logging import itertools +from six.moves import range from curtsies import fsarray, fmtstr from curtsies.formatstring import linesplit diff --git a/bpython/history.py b/bpython/history.py index 15335e0c7..a12a1e3b1 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -24,6 +24,7 @@ import codecs import os from itertools import islice +from six.moves import range from bpython.translations import _ from bpython.filelock import FileLock diff --git a/bpython/keys.py b/bpython/keys.py index 0553fe56f..47cf677f5 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -22,6 +22,7 @@ # import string +from six.moves import range class KeyMap: @@ -46,6 +47,7 @@ def __delitem__(self, key): def __setitem__(self, key, value): self.map[key] = value + cli_key_dispatch = KeyMap(tuple()) urwid_key_dispatch = KeyMap('') diff --git a/bpython/urwid.py b/bpython/urwid.py index e521393b9..2acc270b1 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -42,6 +42,7 @@ import signal from types import ModuleType from optparse import Option +from six.moves import range from pygments.token import Token From 984422d1a9ce78426b0824f4660c0b719c8e1612 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 18:59:10 +0100 Subject: [PATCH 0357/1650] Revert "Use methods without iter" This reverts commit f95b6b4b674de3b933af06d72b77cd0a28352b40. --- bpython/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 4c2a15874..7f87655a6 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -48,11 +48,11 @@ def default_config_path(): def fill_config_with_default_values(config, default_values): - for section in default_values: + for section in default_values.iterkeys(): if not config.has_section(section): config.add_section(section) - for (opt, val) in default_values[section].items(): + for (opt, val) in default_values[section].iteritems(): if not config.has_option(section, opt): config.set(section, opt, str(val)) @@ -130,7 +130,7 @@ def loadini(struct, configfile): }} default_keys_to_commands = dict((value, key) for (key, value) - in defaults['keyboard'].items()) + in defaults['keyboard'].iteritems()) fill_config_with_default_values(config, defaults) if not config.read(config_path): @@ -307,6 +307,6 @@ def load_theme(struct, path, colors, default_colors): colors[k] = theme.get('interface', k) # Check against default theme to see if all values are defined - for k, v in default_colors.items(): + for k, v in default_colors.iteritems(): if k not in colors: colors[k] = v From 706485835fbcc1035ea514847ccee7183f6984d3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 18:59:21 +0100 Subject: [PATCH 0358/1650] Revert "Use methods without iter" This reverts commit 1ef719c6308006b8b7233a6ad9a5940fcaa8c9f0. --- bpython/curtsiesfrontend/interpreter.py | 2 +- bpython/formatter.py | 2 +- bpython/repl.py | 2 +- bpython/urwid.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index c1d341bb0..c8e6d150c 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -45,7 +45,7 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} - for k, v in color_scheme.items(): + for k, v in color_scheme.iteritems(): self.f_strings[k] = '\x01%s' % (v,) Formatter.__init__(self, **options) diff --git a/bpython/formatter.py b/bpython/formatter.py index 765a15a85..8586102aa 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -85,7 +85,7 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} - for k, v in theme_map.items(): + for k, v in theme_map.iteritems(): self.f_strings[k] = '\x01%s' % (color_scheme[v],) if k is Parenthesis: # FIXME: Find a way to make this the inverse of the current diff --git a/bpython/repl.py b/bpython/repl.py index c7ecb2453..c881d2b97 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -958,7 +958,7 @@ def tokenize(self, s, newline=False): else: stack.append((line, len(line_tokens) - 1, line_tokens, value)) - elif value in parens.values(): + elif value in parens.itervalues(): saved_stack = list(stack) try: while True: diff --git a/bpython/urwid.py b/bpython/urwid.py index 2acc270b1..9b56ee784 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1151,7 +1151,7 @@ def main(args=None, locals_=None, banner=None): palette = [ (name, COLORMAP[color.lower()], 'default', 'bold' if color.isupper() else 'default') - for name, color in config.color_scheme.items()] + for name, color in config.color_scheme.iteritems()] palette.extend([ ('bold ' + name, color + ',bold', background, monochrome) for name, color, background, monochrome in palette]) From d513d3aba091c2131a144fee1fc28b9a5cba5d83 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 19:04:58 +0100 Subject: [PATCH 0359/1650] Use iter* from six Signed-off-by: Sebastian Ramacher --- bpython/config.py | 10 ++++++---- bpython/curtsiesfrontend/interpreter.py | 10 ++++++---- bpython/curtsiesfrontend/repl.py | 2 +- bpython/formatter.py | 3 ++- bpython/repl.py | 3 ++- bpython/urwid.py | 3 ++- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 7f87655a6..7bcc0e62d 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -5,6 +5,8 @@ import sys import locale from itertools import chain +from six import iterkeys, iteritems + from bpython.keys import cli_key_dispatch as key_dispatch from bpython.autocomplete import SIMPLE as default_completion import bpython.autocomplete @@ -48,11 +50,11 @@ def default_config_path(): def fill_config_with_default_values(config, default_values): - for section in default_values.iterkeys(): + for section in iterkeys(default_values): if not config.has_section(section): config.add_section(section) - for (opt, val) in default_values[section].iteritems(): + for (opt, val) in iteritems(default_values[section]): if not config.has_option(section, opt): config.set(section, opt, str(val)) @@ -130,7 +132,7 @@ def loadini(struct, configfile): }} default_keys_to_commands = dict((value, key) for (key, value) - in defaults['keyboard'].iteritems()) + in iteritems(defaults['keyboard'])) fill_config_with_default_values(config, defaults) if not config.read(config_path): @@ -307,6 +309,6 @@ def load_theme(struct, path, colors, default_colors): colors[k] = theme.get('interface', k) # Check against default theme to see if all values are defined - for k, v in default_colors.iteritems(): + for k, v in iteritems(default_colors): if k not in colors: colors[k] = v diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index c8e6d150c..82c11380b 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,15 +1,17 @@ import code import traceback import sys +from codeop import CommandCompiler +from six import iteritems + from pygments.token import Generic, Token, Keyword, Name, Comment, String from pygments.token import Error, Literal, Number, Operator, Punctuation from pygments.token import Whitespace from pygments.formatter import Formatter -from bpython.curtsiesfrontend.parse import parse -from bpython.repl import RuntimeTimer -from codeop import CommandCompiler from pygments.lexers import get_lexer_by_name +from bpython.curtsiesfrontend.parse import parse +from bpython.repl import RuntimeTimer default_colors = { Generic.Error:'R', @@ -45,7 +47,7 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} - for k, v in color_scheme.iteritems(): + for k, v in iteritems(color_scheme): self.f_strings[k] = '\x01%s' % (v,) Formatter.__init__(self, **options) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d12722b37..102999178 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -723,7 +723,7 @@ def send_current_block_to_external_editor(self, filename=None): def send_session_to_external_editor(self, filename=None): for_editor = u'### current bpython session - file will be reevaluated, ### lines will not be run\n' - for_editor += '\n'.join(line[len(self.ps1):] if line.startswith(self.ps1) else + for_editor += u'\n'.join(line[len(self.ps1):] if line.startswith(self.ps1) else (line[len(self.ps2):] if line.startswith(self.ps2) else '### '+line) for line in self.getstdout().split('\n')) diff --git a/bpython/formatter.py b/bpython/formatter.py index 8586102aa..796b00043 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -27,6 +27,7 @@ from pygments.formatter import Formatter from pygments.token import Keyword, Name, Comment, String, Error, \ Number, Operator, Token, Whitespace, Literal, Punctuation +from six import iteritems """These format strings are pretty ugly. \x01 represents a colour marker, which @@ -85,7 +86,7 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} - for k, v in theme_map.iteritems(): + for k, v in iteritems(theme_map): self.f_strings[k] = '\x01%s' % (color_scheme[v],) if k is Parenthesis: # FIXME: Find a way to make this the inverse of the current diff --git a/bpython/repl.py b/bpython/repl.py index c881d2b97..d0b669545 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -40,6 +40,7 @@ from itertools import takewhile from locale import getpreferredencoding from string import Template +from six import itervalues from pygments.token import Token @@ -958,7 +959,7 @@ def tokenize(self, s, newline=False): else: stack.append((line, len(line_tokens) - 1, line_tokens, value)) - elif value in parens.itervalues(): + elif value in itervalues(parens): saved_stack = list(stack) try: while True: diff --git a/bpython/urwid.py b/bpython/urwid.py index 9b56ee784..8e6d0c10f 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -43,6 +43,7 @@ from types import ModuleType from optparse import Option from six.moves import range +from six import iteritems from pygments.token import Token @@ -1151,7 +1152,7 @@ def main(args=None, locals_=None, banner=None): palette = [ (name, COLORMAP[color.lower()], 'default', 'bold' if color.isupper() else 'default') - for name, color in config.color_scheme.iteritems()] + for name, color in iteritems(config.color_scheme)] palette.extend([ ('bold ' + name, color + ',bold', background, monochrome) for name, color, background, monochrome in palette]) From 1d5f2e9960b9b7400268e872e21813a7314b0606 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 19:10:14 +0100 Subject: [PATCH 0360/1650] Use six.moves.filter Signed-off-by: Sebastian Ramacher --- bpython/importcompletion.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 8c8cfe5f5..ac5f4b0bf 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -29,6 +29,7 @@ import sys import warnings from warnings import catch_warnings +from six.moves import filter if sys.version_info[0] == 3 and sys.version_info[1] >= 3: import importlib.machinery From 88bd390540699bcf82a7e967fafacdd898000c36 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 19:58:59 +0100 Subject: [PATCH 0361/1650] Use next(iterator) Signed-off-by: Sebastian Ramacher --- bpython/importcompletion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index ac5f4b0bf..4b0c55153 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -198,7 +198,7 @@ def find_coroutine(): return None try: - find_iterator.next() + next(find_iterator) except StopIteration: fully_loaded = True From df0ee5aa2adb1b60e1c88ad6454134b48f12ceb5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 19:59:12 +0100 Subject: [PATCH 0362/1650] Use six.moves.urllib_parse --- bpython/repl.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index d0b669545..b21bcba16 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -41,6 +41,7 @@ from locale import getpreferredencoding from string import Template from six import itervalues +from six.moves.urllib_parse import quote as urlquote, urljoin, urlparse from pygments.token import Token @@ -53,15 +54,6 @@ import bpython.autocomplete as autocomplete -try: - from urllib.parse import quote as urlquote - from urllib.parse import urljoin - from urllib.parse import urlparse -except ImportError: - from urllib import quote as urlquote - from urlparse import urlparse, urljoin - - class RuntimeTimer(object): def __init__(self): self.reset_timer() From c3a1fcd93b5fe67168541f5c1172f8302d08efec Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 19:59:18 +0100 Subject: [PATCH 0363/1650] Fix logic Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 102999178..3209634d7 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1489,7 +1489,7 @@ def request_refresh(): r.width = 50 r.height = 10 while True: - while not importcompletion.find_coroutine(): + while importcompletion.find_coroutine(): pass r.dumb_print_output() r.dumb_input(refreshes) From 8b2c03b4ee05c5fcb82ce3361b01c0b5b848f22d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 21:24:50 +0100 Subject: [PATCH 0364/1650] Revert "Use io.BytesIO instead of StringsIO.StringsIO" This reverts commit 7e0653bd4b295b4f855e55910ac0864922c57a32. --- bpython/test/test_curtsies_repl.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 40958dc5e..be7e22518 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -5,6 +5,7 @@ from contextlib import contextmanager from mock import Mock, patch, MagicMock import os +from StringIO import StringIO import sys import tempfile @@ -13,11 +14,6 @@ except ImportError: import unittest -try: - from io import BytesIO -except ImportError: - from StringIO import StringIO as BytesIO - from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter from bpython import autocomplete @@ -205,7 +201,7 @@ def test_list_win_not_visible_and_match_selected_if_one_option(self): @contextmanager # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy def captured_output(): - new_out, new_err = BytesIO(), BytesIO() + new_out, new_err = StringIO(), StringIO() old_out, old_err = sys.stdout, sys.stderr try: sys.stdout, sys.stderr = new_out, new_err From f5a8e67886991732d82252ea75f7199f3d3e6eba Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 21:25:23 +0100 Subject: [PATCH 0365/1650] Use six.moves.StringIO Signed-off-by: Sebastian Ramacher --- bpython/test/test_curtsies_repl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index be7e22518..e83a35d4e 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -2,12 +2,12 @@ from __future__ import unicode_literals import code -from contextlib import contextmanager -from mock import Mock, patch, MagicMock import os -from StringIO import StringIO import sys import tempfile +from contextlib import contextmanager +from mock import Mock, patch, MagicMock +from six.moves import StringIO try: import unittest2 as unittest From 620da4b5a7f7cdddd23f734c54673a4a1f6e1d76 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 21:43:09 +0100 Subject: [PATCH 0366/1650] A temporary workaround for missing unicode Somebody should take a better look at this. Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3209634d7..53d8c057b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + import contextlib import errno import functools @@ -50,9 +51,9 @@ #TODO other autocomplete modes (also fix in other bpython implementations) - from curtsies.configfile_keynames import keymap as key_dispatch + logger = logging.getLogger(__name__) INCONSISTENT_HISTORY_MSG = u"#<---History inconsistent with output shown--->" @@ -78,6 +79,10 @@ Press {config.edit_config_key} to edit this config file. """ +if py3: + unicode = str + + class FakeStdin(object): """Stdin object user code references so sys.stdin.read() asked user for interactive input""" def __init__(self, coderunner, repl, configured_edit_keys=None): From 918bafd53a501b7ad63ce262100033e0cc3d4703 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 22:03:15 +0100 Subject: [PATCH 0367/1650] Python 3 compatibility fix Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index b21bcba16..ab74989cf 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1101,7 +1101,7 @@ def token_is_type(token): def token_is_any_of(token_types): """Return a callable object that returns whether a token is any of the given types `token_types`.""" - is_token_types = map(token_is, token_types) + is_token_types = tuple(map(token_is, token_types)) def token_is_any_of(token): return any(check(token) for check in is_token_types) From d49acdc0cc93556b7dfc0af8275b878993be894b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 22:04:02 +0100 Subject: [PATCH 0368/1650] Disable 2to3 Signed-off-by: Sebastian Ramacher --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 054bcaf9d..72181c228 100755 --- a/setup.py +++ b/setup.py @@ -237,8 +237,7 @@ def initialize_options(self): }, entry_points = entry_points, cmdclass = cmdclass, - test_suite = 'bpython.test', - use_2to3 = True + test_suite = 'bpython.test' ) # vim: fileencoding=utf-8 sw=4 ts=4 sts=4 ai et sta From d427a5b88f6aedd237e1f09e84e237ed0422c865 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 22:19:13 +0100 Subject: [PATCH 0369/1650] Remove obsolete note Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/tips.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/sphinx/source/tips.rst b/doc/sphinx/source/tips.rst index d6ebdb433..062ce4bf7 100644 --- a/doc/sphinx/source/tips.rst +++ b/doc/sphinx/source/tips.rst @@ -21,12 +21,6 @@ equivalent file. Where the `~/python/bpython`-path is the path to where your bpython source code resides. -.. note:: - - If you want to create the alias for Python 3.X, make sure to run `python3.X - setup.py build` first and point `PYTHONPATH` to the build location (usually - `build/lib`). - You can of course add multiple aliases, so you can run bpython with 2.6, 2.7 and the 3 series. From cece4f1aafd1bcd510b8fef87d32e021fa773ca1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 22:49:37 +0100 Subject: [PATCH 0370/1650] Use six.moves.configparser --- bpython/config.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 7bcc0e62d..fbe94d56d 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -6,16 +6,12 @@ import locale from itertools import chain from six import iterkeys, iteritems +from six.moves.configparser import ConfigParser from bpython.keys import cli_key_dispatch as key_dispatch from bpython.autocomplete import SIMPLE as default_completion import bpython.autocomplete -try: - from configparser import ConfigParser -except ImportError: - from ConfigParser import ConfigParser - class Struct(object): """Simple class for instantiating objects we can add arbitrary attributes From 12aaf9bca03153d8b62f7c50255e843835fc2b07 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 22:49:48 +0100 Subject: [PATCH 0371/1650] Document workaround Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 53d8c057b..95e0ba3e5 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -79,6 +79,7 @@ Press {config.edit_config_key} to edit this config file. """ +# This is needed for is_nop and should be removed once is_nop is fixed. if py3: unicode = str From 17f6eae51bfb9371f343c747f75d15d7dc84c8c2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 22:54:38 +0100 Subject: [PATCH 0372/1650] Use next(iter) Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 2 +- bpython/curtsiesfrontend/repl.py | 2 +- bpython/test/test_crashers.py | 4 ++-- bpython/urwid.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index e00148222..282e4917a 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1453,7 +1453,7 @@ def tab(self, back=False): # 4. swap current word for a match list item elif self.matches_iter.matches: current_match = back and self.matches_iter.previous() \ - or self.matches_iter.next() + or next(self.matches_iter) try: self.show_list(self.matches_iter.matches, topline=self.argspec, formatter=self.matches_iter.completer.format, diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 95e0ba3e5..333a4b611 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -668,7 +668,7 @@ def only_whitespace_left_of_cursor(): elif self.matches_iter.matches: self.current_match = back and self.matches_iter.previous() \ - or self.matches_iter.next() + or next(self.matches_iter) self._cursor_offset, self._current_line = self.matches_iter.cur_line() # using _current_line so we don't trigger a completion reset self.list_win_visible = True diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 037f8954e..8c94521f5 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -59,7 +59,7 @@ def __init__(self): self.data = "" self.delayed_call = None self.states = iter(self.STATES) - self.state = self.states.next() + self.state = next(self.states) def outReceived(self, data): self.data += data @@ -74,7 +74,7 @@ def next(self): if index >= 0: self.data = self.data[index + 4:] self.transport.write(input) - self.state = self.states.next() + self.state = next(self.states) else: self.transport.closeStdin() if self.transport.pid is not None: diff --git a/bpython/urwid.py b/bpython/urwid.py index 8e6d0c10f..6cad9e4de 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1107,7 +1107,7 @@ def tab(self, back=False): if back: self.matches_iter.previous() else: - self.matches_iter.next() + next(self.matches_iter) cursor, text = self.matches_iter.cur_line() self.edit.set_edit_text(text) self.edit.edit_pos = cursor From 476e2160d48b906d1043430aeeb7e4613c0db377 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 22:55:00 +0100 Subject: [PATCH 0373/1650] Whitespaces Signed-off-by: Sebastian Ramacher --- bpython/config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index fbe94d56d..0c30ff001 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -32,14 +32,13 @@ def can_encode(c): def supports_box_chars(): """Check if the encoding suppors Unicode box characters.""" - return all(map( can_encode, u'│─└┘┌┐')) + return all(map(can_encode, u'│─└┘┌┐')) def get_config_home(): """Returns the base directory for bpython's configuration files.""" xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config') return os.path.join(xdg_config_home, 'bpython') - def default_config_path(): """Returns bpython's default configuration file path.""" return os.path.join(get_config_home(), 'config') From 6dfb7fc8507477c595439b9791d4d38ca0f312cc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 23:36:18 +0100 Subject: [PATCH 0374/1650] Removed unused imports Signed-off-by: Sebastian Ramacher --- bpython/_py3compat.py | 1 - bpython/cli.py | 1 - bpython/test/test_curtsies_painting.py | 11 ++++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index e784d8851..dd3c489fd 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -35,7 +35,6 @@ """ import sys -import locale py3 = (sys.version_info[0] == 3) diff --git a/bpython/cli.py b/bpython/cli.py index 282e4917a..cfb8cf2e4 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -58,7 +58,6 @@ import unicodedata import errno -import locale from types import ModuleType from six.moves import range diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 96ec22ba2..933531c9c 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -4,11 +4,6 @@ import os from contextlib import contextmanager -try: - import unittest2 as unittest -except ImportError: - import unittest - from curtsies.formatstringarray import FormatStringTest, fsarray from curtsies.fmtfuncs import cyan, bold, green, yellow, on_magenta, red from bpython.curtsiesfrontend.events import RefreshRequestEvent @@ -20,11 +15,13 @@ from bpython.curtsiesfrontend.repl import INCONSISTENT_HISTORY_MSG, CONTIGUITY_BROKEN_MSG from bpython.test import FixLanguageTestCase as TestCase + def setup_config(): config_struct = config.Struct() config.loadini(config_struct, os.devnull) return config_struct + class TestCurtsiesPainting(FormatStringTest, TestCase): def setUp(self): self.repl = Repl(config=setup_config()) @@ -42,11 +39,13 @@ def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None): if cursor_row_col is not None: self.assertEqual(cursor_pos, cursor_row_col) + class TestCurtsiesPaintingTest(TestCurtsiesPainting): def test_history_is_cleared(self): self.assertEqual(self.repl.rl_history.entries, ['']) + class TestCurtsiesPaintingSimple(TestCurtsiesPainting): def test_startup(self): @@ -122,6 +121,7 @@ def test_paint_lasts_events(self): self.assertFSArraysEqualIgnoringFormatting(actual, expected) + @contextmanager def output_to_repl(repl): old_out, old_err = sys.stdout, sys.stderr @@ -131,6 +131,7 @@ def output_to_repl(repl): finally: sys.stdout, sys.stderr = old_out, old_err + class TestCurtsiesRewindRedraw(TestCurtsiesPainting): def refresh(self): self.refresh_requests.append(RefreshRequestEvent()) From 76c0ae7b890e89d950945b0fd8cedab66c4bce71 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Feb 2015 23:40:39 +0100 Subject: [PATCH 0375/1650] Python 3 compatibility Signed-off-by: Sebastian Ramacher --- bpython/urwid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index 6cad9e4de..995d8c52c 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -934,7 +934,7 @@ def push(self, s, insert_into_history=True): # Pretty blindly adapted from bpython.cli try: return repl.Repl.push(self, s, insert_into_history) - except SystemExit, e: + except SystemExit as e: self.exit_value = e.args raise urwid.ExitMainLoop() except KeyboardInterrupt: From 5d59275c81856e2f651d324d52c4803410b9d851 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 Feb 2015 13:24:35 +0100 Subject: [PATCH 0376/1650] Mock next/__next__ It is not enough to add them to a MagicMock object, since the lookup is magic methods is special. Signed-off-by: Sebastian Ramacher --- bpython/test/__init__.py | 13 +++++++++++++ bpython/test/test_curtsies_repl.py | 8 +++++--- bpython/test/test_repl.py | 5 +++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index 16530c33a..6f24069e6 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -1,12 +1,25 @@ +# -*- coding: utf-8 -*- + try: import unittest2 as unittest except ImportError: import unittest +from mock import MagicMock, Mock + from bpython.translations import init +from bpython._py3compat import py3 class FixLanguageTestCase(unittest.TestCase): @classmethod def setUpClass(cls): init(languages=['en']) + + +class MagicIterMock(MagicMock): + + if py3: + __next__ = Mock(return_value=None) + else: + next = Mock(return_value=None) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index e83a35d4e..f94ad22df 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -6,7 +6,7 @@ import sys import tempfile from contextlib import contextmanager -from mock import Mock, patch, MagicMock +from mock import Mock, patch from six.moves import StringIO try: @@ -20,7 +20,8 @@ from bpython import config from bpython import args from bpython._py3compat import py3 -from bpython.test import FixLanguageTestCase as TestCase +from bpython.test import FixLanguageTestCase as TestCase, MagicIterMock + def setup_config(conf): config_struct = config.Struct() @@ -86,6 +87,7 @@ def test_get_last_word_with_prev_line(self): self.repl.up_one_line() self.assertEqual(self.repl.current_line,'2 3') + def mock_next(obj, return_value): if py3: obj.__next__.return_value = return_value @@ -96,7 +98,7 @@ class TestCurtsiesReplTab(TestCase): def setUp(self): self.repl = create_repl() - self.repl.matches_iter = MagicMock() + self.repl.matches_iter = MagicIterMock() def add_matches(*args, **kwargs): self.repl.matches_iter.matches = ['aaa', 'aab', 'aac'] self.repl.complete = Mock(side_effect=add_matches, diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 2a0545569..e13f36e2e 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -3,7 +3,7 @@ import os import socket -from mock import Mock, MagicMock +from mock import Mock try: import unittest2 as unittest @@ -12,6 +12,7 @@ from bpython._py3compat import py3 from bpython import config, repl, cli, autocomplete +from bpython.test import MagicIterMock def setup_config(conf): @@ -373,7 +374,7 @@ def setUp(self): # 3 Types of tab complete def test_simple_tab_complete(self): - self.repl.matches_iter = MagicMock() + self.repl.matches_iter = MagicIterMock() if py3: self.repl.matches_iter.__bool__.return_value = False else: From 932bfe5299c8b66b3b99d0f7fa7209a210acc0a7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 Feb 2015 13:25:04 +0100 Subject: [PATCH 0377/1650] Remove 2to3 workaround Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index ab74989cf..173e28dda 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -208,16 +208,13 @@ def current(self): raise ValueError('No current match.') return self.matches[self.index] - def _next_impl(self): - """Keep this around until we drop 2to3.""" - self.index = (self.index + 1) % len(self.matches) - return self.matches[self.index] - def next(self): - return self._next_impl() + return self.__next__() def __next__(self): - return self._next_impl() + """Keep this around until we drop 2to3.""" + self.index = (self.index + 1) % len(self.matches) + return self.matches[self.index] def previous(self): if self.index <= 0: From 589a867b2c66196320222b033643b48bb9ceb39e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 Feb 2015 15:29:37 +0100 Subject: [PATCH 0378/1650] PEP-8 Signed-off-by: Sebastian Ramacher --- bpython/clipboard.py | 5 +++++ bpython/config.py | 3 +++ bpython/formatter.py | 3 ++- bpython/inspection.py | 2 ++ bpython/keys.py | 2 +- bpython/pager.py | 1 + bpython/test/__init__.py | 1 + bpython/test/test_args.py | 1 + bpython/test/test_config.py | 5 +++-- bpython/test/test_history.py | 2 +- setup.py | 2 +- 11 files changed, 21 insertions(+), 6 deletions(-) diff --git a/bpython/clipboard.py b/bpython/clipboard.py index 25302863d..8df6211a7 100644 --- a/bpython/clipboard.py +++ b/bpython/clipboard.py @@ -25,9 +25,11 @@ import platform from locale import getpreferredencoding + class CopyFailed(Exception): pass + class XClipboard(object): """Manage clipboard with xclip.""" @@ -38,6 +40,7 @@ def copy(self, content): if process.returncode != 0: raise CopyFailed() + class OSXClipboard(object): """Manage clipboard with pbcopy.""" @@ -47,6 +50,7 @@ def copy(self, content): if process.returncode != 0: raise CopyFailed() + def command_exists(command): process = subprocess.Popen(['which', command], stderr=subprocess.STDOUT, stdout=subprocess.PIPE) @@ -54,6 +58,7 @@ def command_exists(command): return process.returncode == 0 + def get_clipboard(): """Get best clipboard handling implemention for current system.""" diff --git a/bpython/config.py b/bpython/config.py index 0c30ff001..5bc98e1ef 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -30,15 +30,18 @@ def can_encode(c): except UnicodeEncodeError: return False + def supports_box_chars(): """Check if the encoding suppors Unicode box characters.""" return all(map(can_encode, u'│─└┘┌┐')) + def get_config_home(): """Returns the base directory for bpython's configuration files.""" xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config') return os.path.join(xdg_config_home, 'bpython') + def default_config_path(): """Returns bpython's default configuration file path.""" return os.path.join(get_config_home(), 'config') diff --git a/bpython/formatter.py b/bpython/formatter.py index 796b00043..0f165830a 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -26,7 +26,8 @@ from pygments.formatter import Formatter from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Token, Whitespace, Literal, Punctuation + Number, Operator, Token, Whitespace, Literal, \ + Punctuation from six import iteritems """These format strings are pretty ugly. diff --git a/bpython/inspection.py b/bpython/inspection.py index 406f56056..d900c4ad8 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -98,6 +98,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): setattr(type_, '__getattr__', __getattr__) # /Dark magic + class _Repr(object): """ Helper for `fixlongargs()`: Returns the given value in `__repr__()`. @@ -111,6 +112,7 @@ def __repr__(self): __str__ = __repr__ + def parsekeywordpairs(signature): tokens = PythonLexer().get_tokens(signature) preamble = True diff --git a/bpython/keys.py b/bpython/keys.py index 47cf677f5..3e0d7288b 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -54,7 +54,7 @@ def __setitem__(self, key, value): # fill dispatch with letters for c in string.ascii_lowercase: cli_key_dispatch['C-%s' % c] = (chr(string.ascii_lowercase.index(c) + 1), - '^%s' % c.upper()) + '^%s' % c.upper()) for c in string.ascii_lowercase: urwid_key_dispatch['C-%s' % c] = 'ctrl %s' % c diff --git a/bpython/pager.py b/bpython/pager.py index 7133f598f..20b1af743 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -29,6 +29,7 @@ import sys import shlex + def get_pager_command(default='less -rf'): command = shlex.split(os.environ.get('PAGER', default)) return command diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index 6f24069e6..b7960f912 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -10,6 +10,7 @@ from bpython.translations import init from bpython._py3compat import py3 + class FixLanguageTestCase(unittest.TestCase): @classmethod diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index bb754d979..daf8d58ee 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -8,6 +8,7 @@ except ImportError: import unittest + class TestExecArgs(unittest.TestCase): def test_exec_dunder_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index ec21926a0..16def998f 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -11,6 +11,7 @@ TEST_THEME_PATH = os.path.join(os.path.dirname(__file__), "test.theme") + class TestConfig(unittest.TestCase): def load_temp_config(self, content, struct=None): """Write config to a temporary file and load it.""" @@ -35,7 +36,8 @@ def test_load_theme(self): defaults = {"name": "c"} expected.update(defaults) - config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, defaults) + config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, + defaults) self.assertEquals(struct.color_scheme, expected) def test_keybindings_default_contains_no_duplicates(self): @@ -90,4 +92,3 @@ def test_keybindings_unused(self): """)) self.assertEqual(struct.help_key, 'F4') - diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index 2362cef8d..401e8ad4f 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -5,6 +5,7 @@ from bpython.history import History + class TestHistory(unittest.TestCase): def setUp(self): self.history = History('#%d' % x for x in range(1000)) @@ -74,7 +75,6 @@ def test_enter_2(self): self.assertEqual(self.history.back(), '#508') self.assertEqual(self.history.forward(), '#509') - def test_reset(self): self.history.enter('#lastnumber!') self.history.reset() diff --git a/setup.py b/setup.py index 72181c228..98e281d38 100755 --- a/setup.py +++ b/setup.py @@ -66,7 +66,7 @@ with open(version_file, 'w') as vf: vf.write('# Auto-generated file, do not edit!\n') - vf.write('__version__=\'%s\'\n' % (version, )) + vf.write('__version__ = \'%s\'\n' % (version, )) class install(_install): """Force install to run build target.""" From 5ac88f46059942301ec18abb9dfb71d899f9fbd4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 Feb 2015 15:29:48 +0100 Subject: [PATCH 0379/1650] Fix translations Signed-off-by: Sebastian Ramacher --- bpython/translations/de/LC_MESSAGES/bpython.po | 6 +++--- bpython/translations/fr_FR/LC_MESSAGES/bpython.po | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 941a867b0..3d81ab2bc 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" "POT-Creation-Date: 2015-02-02 17:34+0100\n" -"PO-Revision-Date: 2015-02-02 00:31+0100\n" +"PO-Revision-Date: 2015-02-05 14:54+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: de \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -181,9 +181,9 @@ msgstr "" "werden." #: bpython/repl.py:789 -#, fuzzy, python-format +#, python-format msgid "Upload failed: Helper program returned non-zero exit status %d." -msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm beendete mit Status %s." +msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm beendete mit Status %d." #: bpython/repl.py:793 msgid "Upload failed: No output from helper program." diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index 940900462..92f31ea59 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" "POT-Creation-Date: 2015-02-02 17:34+0100\n" -"PO-Revision-Date: 2015-02-02 00:31+0100\n" +"PO-Revision-Date: 2015-02-05 14:54+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" @@ -183,11 +183,11 @@ msgid "Upload failed: Helper program could not be run." msgstr "Echec de l'upload: impossible de lancer le programme externe." #: bpython/repl.py:789 -#, fuzzy, python-format +#, python-format msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "" "Echec de l'upload: le programme externe a renvoyé un statut de sortie " -"différent de zéro %s." +"différent de zéro %d." #: bpython/repl.py:793 msgid "Upload failed: No output from helper program." From 997dfb64c2f0de7c901dc5a5e166973e8c190778 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 Feb 2015 15:29:58 +0100 Subject: [PATCH 0380/1650] Use six.moves.range Signed-off-by: Sebastian Ramacher --- bpython/test/test_history.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index 401e8ad4f..e32431d06 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -3,6 +3,8 @@ except ImportError: import unittest +from six.moves import range + from bpython.history import History From 5b406c127f40d2ceb1d6d9de4a540054e1582e48 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 Feb 2015 13:38:46 +0100 Subject: [PATCH 0381/1650] PEP-8 Signed-off-by: Sebastian Ramacher --- bpython/args.py | 23 +++-- bpython/autocomplete.py | 7 +- bpython/clipboard.py | 3 +- bpython/config.py | 4 +- bpython/curtsies.py | 46 ++++++--- bpython/formatter.py | 3 +- bpython/inspection.py | 6 +- bpython/line.py | 38 ++++--- bpython/repl.py | 126 ++++++++++++++--------- bpython/test/fodder/encoding_ascii.py | 1 + bpython/test/fodder/encoding_latin1.py | 1 + bpython/test/fodder/encoding_utf8.py | 1 + bpython/test/test_autocomplete.py | 43 +++++--- bpython/test/test_crashers.py | 12 ++- bpython/test/test_curtsies_coderunner.py | 9 +- bpython/test/test_curtsies_painting.py | 45 +++++--- bpython/test/test_curtsies_parser.py | 13 ++- bpython/test/test_curtsies_repl.py | 41 +++++--- bpython/test/test_filewatch.py | 2 +- bpython/test/test_importcompletion.py | 25 ++--- bpython/test/test_interpreter.py | 13 ++- bpython/test/test_keys.py | 10 +- bpython/test/test_line_properties.py | 51 +++++++-- bpython/test/test_manual_readline.py | 79 +++++++------- bpython/test/test_preprocess.py | 12 ++- bpython/test/test_repl.py | 65 ++++++------ bpython/translations/__init__.py | 2 +- 27 files changed, 432 insertions(+), 249 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index d98820bfd..da02d56c3 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -24,7 +24,8 @@ def error(self, msg): def version_banner(): return 'bpython version %s on top of Python %s %s' % ( - __version__, sys.version.split()[0], sys.executable) + __version__, sys.version.split()[0], sys.executable) + def parse(args, extras=None, ignore_stdin=False): """Receive an argument list - if None, use sys.argv - parse all args and @@ -37,11 +38,12 @@ def parse(args, extras=None, ignore_stdin=False): e.g.: - parse(['-i', '-m', 'foo.py'], - ('Front end-specific options', - 'A full description of what these options are for', - [optparse.Option('-f', action='store_true', dest='f', help='Explode'), - optparse.Option('-l', action='store_true', dest='l', help='Love')])) + parse( + ['-i', '-m', 'foo.py'], + ('Front end-specific options', + 'A full description of what these options are for', + [optparse.Option('-f', action='store_true', dest='f', help='Explode'), + optparse.Option('-l', action='store_true', dest='l', help='Love')])) Return a tuple of (config, options, exec_args) wherein "config" is the @@ -55,9 +57,9 @@ def parse(args, extras=None, ignore_stdin=False): parser = RaisingOptionParser( usage=_('Usage: %prog [options] [file [args]]\n' - 'NOTE: If bpython sees an argument it does ' - 'not know, execution falls back to the ' - 'regular Python interpreter.')) + 'NOTE: If bpython sees an argument it does ' + 'not know, execution falls back to the ' + 'regular Python interpreter.')) # This is not sufficient if bpython gains its own -m support # (instead of falling back to Python itself for that). # That's probably fixable though, for example by having that @@ -67,7 +69,7 @@ def parse(args, extras=None, ignore_stdin=False): help=_('Use CONFIG instead of default config file.')) parser.add_option('--interactive', '-i', action='store_true', help=_('Drop to bpython shell after running file ' - 'instead of exiting.')) + 'instead of exiting.')) parser.add_option('--quiet', '-q', action='store_true', help=_("Don't flush the output to stdout.")) parser.add_option('--version', '-V', action='store_true', @@ -102,6 +104,7 @@ def parse(args, extras=None, ignore_stdin=False): return config, options, args + def exec_code(interpreter, args): """ Helper to execute code in a given interpreter. args should be a [faked] diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 84d582929..a79ecda25 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -288,7 +288,8 @@ def matches(self, cursor_offset, line, **kwargs): matches.add(word) for nspace in [builtins.__dict__, locals_]: for word, val in nspace.items(): - if method_match(word, len(text), text) and word != "__builtins__": + if (method_match(word, len(text), text) and + word != "__builtins__"): matches.add(_callable_postfix(val, word)) return matches @@ -373,7 +374,8 @@ def matches(self, cursor_offset, line, **kwargs): first_letter = line[self._orig_start:self._orig_start+1] matches = [c.name for c in completions] - if any(not m.lower().startswith(matches[0][0].lower()) for m in matches): + if any(not m.lower().startswith(matches[0][0].lower()) + for m in matches): # Too general - giving completions starting with multiple # letters return None @@ -386,7 +388,6 @@ def locate(self, cursor_offset, line): end = cursor_offset return start, end, line[start:end] - class MultilineJediCompletion(JediCompletion): def matches(self, cursor_offset, line, **kwargs): if 'current_block' not in kwargs or 'history' not in kwargs: diff --git a/bpython/clipboard.py b/bpython/clipboard.py index 8df6211a7..200fbfa93 100644 --- a/bpython/clipboard.py +++ b/bpython/clipboard.py @@ -65,7 +65,8 @@ def get_clipboard(): if platform.system() == 'Darwin': if command_exists('pbcopy'): return OSXClipboard() - if platform.system() in ('Linux', 'FreeBSD', 'OpenBSD') and os.getenv('DISPLAY') is not None: + if (platform.system() in ('Linux', 'FreeBSD', 'OpenBSD') and + os.getenv('DISPLAY') is not None): if command_exists('xclip'): return XClipboard() diff --git a/bpython/config.py b/bpython/config.py index 5bc98e1ef..9cf454374 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -148,8 +148,8 @@ def get_key_no_doublebind(command): try: default_command = default_keys_to_commands[requested_key] - if default_commands_to_keys[default_command] == \ - config.get('keyboard', default_command): + if (default_commands_to_keys[default_command] == + config.get('keyboard', default_command)): setattr(struct, '%s_key' % default_command, '') except KeyError: pass diff --git a/bpython/curtsies.py b/bpython/curtsies.py index ffa83645b..7a2b4246d 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -33,7 +33,8 @@ def main(args=None, locals_=None, banner=None): Option('--log', '-L', action='count', help=_("log debug messages to bpython.log")), Option('--type', '-t', action='store_true', - help=_("enter lines of file as though interactively typed")), + help=_("enter lines of file as though interactively " + "typed")), ])) if options.log is None: options.log = 0 @@ -67,13 +68,18 @@ def main(args=None, locals_=None, banner=None): if not options.interactive: raise SystemExit(exit_value) else: - sys.path.insert(0, '') # expected for interactive sessions (vanilla python does it) + # expected for interactive sessions (vanilla python does it) + sys.path.insert(0, '') print(bpargs.version_banner()) - mainloop(config, locals_, banner, interp, paste, interactive=(not exec_args)) + mainloop(config, locals_, banner, interp, paste, + interactive=(not exec_args)) -def mainloop(config, locals_, banner, interp=None, paste=None, interactive=True): - with curtsies.input.Input(keynames='curtsies', sigint_event=True) as input_generator: + +def mainloop(config, locals_, banner, interp=None, paste=None, + interactive=True): + with curtsies.input.Input(keynames='curtsies', sigint_event=True) as \ + input_generator: with curtsies.window.CursorAwareWindow( sys.stdout, sys.stdin, @@ -81,11 +87,16 @@ def mainloop(config, locals_, banner, interp=None, paste=None, interactive=True) hide_cursor=False, extra_bytes_callback=input_generator.unget_bytes) as window: - request_refresh = input_generator.event_trigger(bpythonevents.RefreshRequestEvent) - schedule_refresh = input_generator.scheduled_event_trigger(bpythonevents.ScheduledRefreshRequestEvent) - request_reload = input_generator.threadsafe_event_trigger(bpythonevents.ReloadEvent) - interrupting_refresh = input_generator.threadsafe_event_trigger(lambda: None) - request_undo = input_generator.event_trigger(bpythonevents.UndoEvent) + request_refresh = input_generator.event_trigger( + bpythonevents.RefreshRequestEvent) + schedule_refresh = input_generator.scheduled_event_trigger( + bpythonevents.ScheduledRefreshRequestEvent) + request_reload = input_generator.threadsafe_event_trigger( + bpythonevents.ReloadEvent) + interrupting_refresh = input_generator.threadsafe_event_trigger( + lambda: None) + request_undo = input_generator.event_trigger( + bpythonevents.UndoEvent) def on_suspend(): window.__exit__(None, None, None) @@ -96,7 +107,8 @@ def after_suspend(): window.__enter__() interrupting_refresh() - global repl # global for easy introspection `from bpython.curtsies import repl` + # global for easy introspection `from bpython.curtsies import repl` + global repl with Repl(config=config, locals_=locals_, request_refresh=request_refresh, @@ -119,7 +131,10 @@ def process_event(e): if e is not None: repl.process_event(e) except (SystemExitFromCodeGreenlet, SystemExit) as err: - array, cursor_pos = repl.paint(about_to_exit=True, user_quit=isinstance(err, SystemExitFromCodeGreenlet)) + array, cursor_pos = repl.paint( + about_to_exit=True, + user_quit=isinstance(err, + SystemExitFromCodeGreenlet)) scrolled = window.render_to_terminal(array, cursor_pos) repl.scroll_offset += scrolled raise @@ -134,7 +149,8 @@ def process_event(e): repl.coderunner.interp.locals['_repl'] = repl repl.coderunner.interp.runsource( - 'from bpython.curtsiesfrontend._internal import _Helper') + 'from bpython.curtsiesfrontend._internal ' + 'import _Helper') repl.coderunner.interp.runsource('help = _Helper(_repl)\n') del repl.coderunner.interp.locals['_repl'] @@ -147,7 +163,8 @@ def process_event(e): if paste: process_event(paste) - process_event(None) # do a display before waiting for first event + # do a display before waiting for first event + process_event(None) for unused in find_iterator: e = input_generator.send(0) if e is not None: @@ -156,5 +173,6 @@ def process_event(e): for e in input_generator: process_event(e) + if __name__ == '__main__': sys.exit(main()) diff --git a/bpython/formatter.py b/bpython/formatter.py index 0f165830a..557ecda5e 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -26,8 +26,7 @@ from pygments.formatter import Formatter from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Token, Whitespace, Literal, \ - Punctuation + Number, Operator, Token, Whitespace, Literal, Punctuation from six import iteritems """These format strings are pretty ugly. diff --git a/bpython/inspection.py b/bpython/inspection.py index d900c4ad8..e94edd8ed 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -134,7 +134,7 @@ def parsekeywordpairs(signature): # End of signature reached break if ((value == ',' and parendepth == 0) or - (value == ')' and parendepth == -1)): + (value == ')' and parendepth == -1)): stack.append(substack) substack = [] continue @@ -231,8 +231,8 @@ def getargspec(func, f): try: is_bound_method = ((inspect.ismethod(f) and f.im_self is not None) - or (func_name == '__init__' and not - func.endswith('.__init__'))) + or (func_name == '__init__' and not + func.endswith('.__init__'))) except: # if f is a method from a xmlrpclib.Server instance, func_name == # '__init__' throws xmlrpclib.Fault (see #202) diff --git a/bpython/line.py b/bpython/line.py index 0a2ca045d..12f753142 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -53,14 +53,16 @@ def current_dict(cursor_offset, line): current_string_re = LazyReCompile( - '''(?P(?:""")|"|(?:''\')|')(?:((?P.+?)(?P=open))|(?P.+))''') + '''(?P(?:""")|"|(?:''\')|')(?:((?P.+?)(?P=open))|''' + '''(?P.+))''') def current_string(cursor_offset, line): - """If inside a string of nonzero length, return the string (excluding quotes) + """If inside a string of nonzero length, return the string (excluding + quotes) - Weaker than bpython.Repl's current_string, because that checks that a string is a string - based on previous lines in the buffer""" + Weaker than bpython.Repl's current_string, because that checks that a + string is a string based on previous lines in the buffer.""" for m in current_string_re.finditer(line): i = 3 if m.group(3) else 4 if m.start(i) <= cursor_offset and m.end(i) >= cursor_offset: @@ -72,9 +74,11 @@ def current_string(cursor_offset, line): def current_object(cursor_offset, line): - """If in attribute completion, the object on which attribute should be looked up""" + """If in attribute completion, the object on which attribute should be + looked up.""" match = current_word(cursor_offset, line) - if match is None: return None + if match is None: + return None start, end, word = match matches = current_object_re.finditer(word) s = '' @@ -94,33 +98,36 @@ def current_object(cursor_offset, line): def current_object_attribute(cursor_offset, line): """If in attribute completion, the attribute being completed""" match = current_word(cursor_offset, line) - if match is None: return None + if match is None: + return None start, end, word = match matches = current_object_attribute_re.finditer(word) next(matches) for m in matches: - if m.start(1) + start <= cursor_offset and m.end(1) + start >= cursor_offset: + if (m.start(1) + start <= cursor_offset and + m.end(1) + start >= cursor_offset): return m.start(1) + start, m.end(1) + start, m.group(1) return None -current_from_import_from_re = LazyReCompile(r'from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*') +current_from_import_from_re = LazyReCompile( + r'from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*') def current_from_import_from(cursor_offset, line): """If in from import completion, the word after from - returns None if cursor not in or just after one of the two interesting parts - of an import: from (module) import (name1, name2) + returns None if cursor not in or just after one of the two interesting + parts of an import: from (module) import (name1, name2) """ - #TODO allow for as's + # TODO allow for as's tokens = line.split() if not ('from' in tokens or 'import' in tokens): return None matches = current_from_import_from_re.finditer(line) for m in matches: if ((m.start(1) < cursor_offset and m.end(1) >= cursor_offset) or - (m.start(2) < cursor_offset and m.end(2) >= cursor_offset)): + (m.start(2) < cursor_offset and m.end(2) >= cursor_offset)): return m.start(1), m.end(1), m.group(1) return None @@ -156,7 +163,7 @@ def current_from_import_import(cursor_offset, line): def current_import(cursor_offset, line): - #TODO allow for multiple as's + # TODO allow for multiple as's baseline = current_import_re_1.search(line) if baseline is None: return None @@ -207,7 +214,8 @@ def current_dotted_attribute(cursor_offset, line): current_string_literal_attr_re = LazyReCompile( "('''" + - r'''|"""|'|")((?:(?=([^"'\\]+|\\.|(?!\1)["']))\3)*)\1[.]([a-zA-Z_]?[\w]*)''') + r'''|"""|'|")''' + + r'''((?:(?=([^"'\\]+|\\.|(?!\1)["']))\3)*)\1[.]([a-zA-Z_]?[\w]*)''') def current_string_literal_attr(cursor_offset, line): diff --git a/bpython/repl.py b/bpython/repl.py index 173e28dda..b70f783e2 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -74,6 +74,7 @@ def reset_timer(self): def estimate(self): return self.running_time - self.last_command + class Interpreter(code.InteractiveInterpreter): def __init__(self, locals=None, encoding=None): @@ -87,7 +88,8 @@ def __init__(self, locals=None, encoding=None): self.encoding = encoding or sys.getdefaultencoding() self.syntaxerror_callback = None - # Unfortunately code.InteractiveInterpreter is a classic class, so no super() + # Unfortunately code.InteractiveInterpreter is a classic class, so no + # super() code.InteractiveInterpreter.__init__(self, locals) self.timer = RuntimeTimer() @@ -107,8 +109,8 @@ def runsource(self, source, filename='', symbol='single', encode=True): with self.timer: if encode: - source = '# coding: %s\n%s' % (self.encoding, - source.encode(self.encoding)) + source = '# coding: %s\n%s' % ( + self.encoding, source.encode(self.encoding)) return code.InteractiveInterpreter.runsource(self, source, filename, symbol) @@ -151,9 +153,9 @@ def showtraceback(self): del tblist[:1] # Set the right lineno (encoding header adds an extra line) if not py3: - for i, (filename, lineno, module, something) in enumerate(tblist): - if filename == '': - tblist[i] = (filename, lineno - 1, module, something) + for i, (fname, lineno, module, something) in enumerate(tblist): + if fname == '': + tblist[i] = (fname, lineno - 1, module, something) l = traceback.format_list(tblist) if l: @@ -181,12 +183,18 @@ class MatchesIterator(object): `update`ed to set what matches will be iterated over.""" def __init__(self): - self.current_word = '' # word being replaced in the original line of text - self.matches = None # possible replacements for current_word - self.index = -1 # which word is currently replacing the current word - self.orig_cursor_offset = None # cursor position in the original line - self.orig_line = None # original line (before match replacements) - self.completer = None # class describing the current type of completion + # word being replaced in the original line of text + self.current_word = '' + # possible replacements for current_word + self.matches = None + # which word is currently replacing the current word + self.index = -1 + # cursor position in the original line + self.orig_cursor_offset = None + # original line (before match replacements) + self.orig_line = None + # class describing the current type of completion + self.completer = None def __nonzero__(self): """MatchesIterator is False when word hasn't been replaced yet""" @@ -197,7 +205,8 @@ def __bool__(self): @property def candidate_selected(self): - """True when word selected/replaced, False when word hasn't been replaced yet""" + """True when word selected/replaced, False when word hasn't been + replaced yet""" return bool(self) def __iter__(self): @@ -224,26 +233,31 @@ def previous(self): return self.matches[self.index] def cur_line(self): - """Returns a cursor offset and line with the current substitution made""" + """Returns a cursor offset and line with the current substitution + made""" return self.substitute(self.current()) def substitute(self, match): """Returns a cursor offset and line with match substituted in""" - start, end, word = self.completer.locate(self.orig_cursor_offset, self.orig_line) - result = start + len(match), self.orig_line[:start] + match + self.orig_line[end:] - return result + start, end, word = self.completer.locate(self.orig_cursor_offset, + self.orig_line) + return (start + len(match), + self.orig_line[:start] + match + self.orig_line[end:]) def is_cseq(self): - return bool(os.path.commonprefix(self.matches)[len(self.current_word):]) + return bool( + os.path.commonprefix(self.matches)[len(self.current_word):]) def substitute_cseq(self): - """Returns a new line by substituting a common sequence in, and update matches""" + """Returns a new line by substituting a common sequence in, and update + matches""" cseq = os.path.commonprefix(self.matches) new_cursor_offset, new_line = self.substitute(cseq) if len(self.matches) == 1: self.clear() else: - self.update(new_cursor_offset, new_line, self.matches, self.completer) + self.update(new_cursor_offset, new_line, self.matches, + self.completer) if len(self.matches) == 1: self.clear() return new_cursor_offset, new_line @@ -251,7 +265,8 @@ def substitute_cseq(self): def update(self, cursor_offset, current_line, matches, completer): """Called to reset the match index and update the word being replaced - Should only be called if there's a target to update - otherwise, call clear""" + Should only be called if there's a target to update - otherwise, call + clear""" if matches is None: raise ValueError("Matches may not be None.") @@ -261,7 +276,8 @@ def update(self, cursor_offset, current_line, matches, completer): self.matches = matches self.completer = completer self.index = -1 - self.start, self.end, self.current_word = self.completer.locate(self.orig_cursor_offset, self.orig_line) + self.start, self.end, self.current_word = self.completer.locate( + self.orig_cursor_offset, self.orig_line) def clear(self): self.matches = [] @@ -272,6 +288,7 @@ def clear(self): self.end = None self.index = -1 + class Interaction(object): def __init__(self, config, statusbar=None): self.config = config @@ -395,7 +412,8 @@ def startup(self): if py3: self.interp.runsource(f.read(), filename, 'exec') else: - self.interp.runsource(f.read(), filename, 'exec', encode=False) + self.interp.runsource(f.read(), filename, 'exec', + encode=False) def current_string(self, concatenate=False): """If the line ends in a string get it, otherwise return ''""" @@ -449,7 +467,7 @@ def get_args(self): stack = [['', 0, '']] try: for (token, value) in PythonLexer().get_tokens( - self.current_line): + self.current_line): if token is Token.Punctuation: if value in '([{': stack.append(['', 0, value]) @@ -548,7 +566,8 @@ def set_docstring(self): self.docstring = None # What complete() does: - # Should we show the completion box? (are there matches, or is there a docstring to show?) + # Should we show the completion box? (are there matches, or is there a + # docstring to show?) # Some completions should always be shown, other only if tab=True # set the current docstring to the "current function's" docstring # Populate the matches_iter object with new matches from the current state @@ -579,7 +598,8 @@ def complete(self, tab=False): current_block='\n'.join(self.buffer + [self.current_line]), complete_magic_methods=self.config.complete_magic_methods, history=self.history) - #TODO implement completer.shown_before_tab == False (filenames shouldn't fill screen) + # TODO implement completer.shown_before_tab == False (filenames + # shouldn't fill screen) if len(matches) == 0: self.matches_iter.clear() @@ -589,9 +609,12 @@ def complete(self, tab=False): self.current_line, matches, completer) if len(matches) == 1: - if tab: # if this complete is being run for a tab key press, substitute common sequence - self._cursor_offset, self._current_line = self.matches_iter.substitute_cseq() - return Repl.complete(self) # again for + if tab: + # if this complete is being run for a tab key press, substitute + # common sequence + self._cursor_offset, self._current_line = \ + self.matches_iter.substitute_cseq() + return Repl.complete(self) # again for elif self.matches_iter.current_word == matches[0]: self.matches_iter.clear() return False @@ -716,7 +739,7 @@ def pastebin(self, s=None): s = self.getstdout() if (self.config.pastebin_confirm and - not self.interact.confirm(_("Pastebin buffer? (y/N) "))): + not self.interact.confirm(_("Pastebin buffer? (y/N) "))): self.interact.notify(_("Pastebin aborted.")) return return self.do_pastebin(s) @@ -724,8 +747,10 @@ def pastebin(self, s=None): def do_pastebin(self, s): """Actually perform the upload.""" if s == self.prev_pastebin_content: - self.interact.notify(_('Duplicate pastebin. Previous URL: %s. Removal URL: %s') % - (self.prev_pastebin_url, self.prev_removal_url), 10) + self.interact.notify(_('Duplicate pastebin. Previous URL: %s. ' + 'Removal URL: %s') % + (self.prev_pastebin_url, + self.prev_removal_url), 10) return self.prev_pastebin_url if self.config.pastebin_helper: @@ -748,8 +773,8 @@ def do_pastebin_json(self, s): response = requests.post(url, data=payload, verify=True) response.raise_for_status() except requests.exceptions.RequestException as exc: - self.interact.notify(_('Upload failed: %s') % (str(exc), )) - return + self.interact.notify(_('Upload failed: %s') % (str(exc), )) + return self.prev_pastebin_content = s data = response.json() @@ -760,10 +785,11 @@ def do_pastebin_json(self, s): removal_url_template = Template(self.config.pastebin_removal_url) removal_id = urlquote(data['removal_id']) - removal_url = removal_url_template.safe_substitute(removal_id=removal_id) + removal_url = removal_url_template.safe_substitute( + removal_id=removal_id) self.prev_pastebin_url = paste_url - self.prev_removal_url = removal_url + self.prev_removal_url = removal_url self.interact.notify(_('Pastebin URL: %s - Removal URL: %s') % (paste_url, removal_url), 10) @@ -803,7 +829,8 @@ def do_pastebin_helper(self, s): else: parsed_url = urlparse(paste_url) if (not parsed_url.scheme - or any(unicodedata.category(c) == 'Cc' for c in paste_url)): + or any(unicodedata.category(c) == 'Cc' + for c in paste_url)): self.interact.notify(_("Upload failed: " "Failed to recognize the helper " "program's output as an URL.")) @@ -839,7 +866,7 @@ def insert_into_history(self, s): def prompt_undo(self): """Returns how many lines to undo, 0 means don't undo""" if (self.config.single_undo_time < 0 or - self.interp.timer.estimate() < self.config.single_undo_time): + self.interp.timer.estimate() < self.config.single_undo_time): return 1 est = self.interp.timer.estimate() n = self.interact.file_prompt( @@ -858,7 +885,8 @@ def prompt_undo(self): return 0 else: message = ngettext('Undoing %d line... (est. %.1f seconds)', - 'Undoing %d lines... (est. %.1f seconds)', n) + 'Undoing %d lines... (est. %.1f seconds)', + n) self.interact.notify(message % (n, est), .1) return n @@ -904,10 +932,10 @@ def tokenize(self, s, newline=False): with side effects/impurities: - reads self.cpos to see what parens should be highlighted - reads self.buffer to see what came before the passed in line - - sets self.highlighted_paren to (buffer_lineno, tokens_for_that_line) for buffer line - that should replace that line to unhighlight it - - calls reprint_line with a buffer's line's tokens and the buffer lineno that has changed - iff that line is the not the current line + - sets self.highlighted_paren to (buffer_lineno, tokens_for_that_line) + for buffer line that should replace that line to unhighlight it + - calls reprint_line with a buffer's line's tokens and the buffer + lineno that has changed iff that line is the not the current line """ source = '\n'.join(self.buffer + [s]) @@ -1021,17 +1049,20 @@ def open_in_external_editor(self, filename): def edit_config(self): if not (os.path.isfile(self.config.config_path)): - if self.interact.confirm(_("Config file does not exist - create new from default? (y/N)")): + if self.interact.confirm(_("Config file does not exist - create " + "new from default? (y/N)")): try: - default_config = pkgutil.get_data('bpython', 'sample-config') + default_config = pkgutil.get_data('bpython', + 'sample-config') bpython_dir, script_name = os.path.split(__file__) - containing_dir = os.path.dirname(os.path.abspath(self.config.config_path)) + containing_dir = os.path.dirname( + os.path.abspath(self.config.config_path)) if not os.path.exists(containing_dir): os.makedirs(containing_dir) with open(self.config.config_path, 'w') as f: f.write(default_config) except (IOError, OSError) as e: - self.interact.notify(_("Error writing file '%s': %s") % \ + self.interact.notify(_("Error writing file '%s': %s") % (self.config.config.path, str(e))) return False else: @@ -1105,6 +1136,7 @@ def token_is_any_of(token): return token_is_any_of + def extract_exit_value(args): """Given the arguments passed to `SystemExit`, return the value that should be passed to `sys.exit`. diff --git a/bpython/test/fodder/encoding_ascii.py b/bpython/test/fodder/encoding_ascii.py index 844c2becd..d9caeab01 100644 --- a/bpython/test/fodder/encoding_ascii.py +++ b/bpython/test/fodder/encoding_ascii.py @@ -1,5 +1,6 @@ # -*- coding: ascii -*- + def foo(): """Test""" pass diff --git a/bpython/test/fodder/encoding_latin1.py b/bpython/test/fodder/encoding_latin1.py index 2f4fd15b5..a74dc3454 100644 --- a/bpython/test/fodder/encoding_latin1.py +++ b/bpython/test/fodder/encoding_latin1.py @@ -1,5 +1,6 @@ # -*- coding: latin1 -*- + def foo(): """Test """ pass diff --git a/bpython/test/fodder/encoding_utf8.py b/bpython/test/fodder/encoding_utf8.py index 933dc4afe..276ef0be0 100644 --- a/bpython/test/fodder/encoding_utf8.py +++ b/bpython/test/fodder/encoding_utf8.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- + def foo(): """Test äöü""" pass diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 847e65703..87f0dbbe7 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -35,23 +35,28 @@ def test_filename(self): def test_attribute(self): self.assertEqual(autocomplete.after_last_dot('abc.edf'), 'edf') + def completer(matches): mock_completer = autocomplete.BaseCompletionType() mock_completer.matches = mock.Mock(return_value=matches) return mock_completer + class TestGetCompleter(unittest.TestCase): def test_no_completers(self): - self.assertTupleEqual(autocomplete.get_completer([], 0, ''), ([], None)) + self.assertTupleEqual(autocomplete.get_completer([], 0, ''), + ([], None)) def test_one_completer_without_matches_returns_empty_list_and_none(self): a = completer([]) - self.assertTupleEqual(autocomplete.get_completer([a], 0, ''), ([], None)) + self.assertTupleEqual(autocomplete.get_completer([a], 0, ''), + ([], None)) def test_one_completer_returns_matches_and_completer(self): a = completer(['a']) - self.assertTupleEqual(autocomplete.get_completer([a], 0, ''), (['a'], a)) + self.assertTupleEqual(autocomplete.get_completer([a], 0, ''), + (['a'], a)) def test_two_completers_with_matches_returns_first_matches(self): a = completer(['a']) @@ -72,6 +77,7 @@ def test_first_completer_returns_None(self): b = completer(['a']) self.assertEqual(autocomplete.get_completer([a, b], 0, ''), (['a'], b)) + class TestCumulativeCompleter(unittest.TestCase): def completer(self, matches, ): @@ -119,7 +125,8 @@ def test_match_returns_none_if_not_in_string(self): def test_match_returns_empty_list_when_no_files(self): self.assertEqual(self.completer.matches(2, '"a'), set()) - @mock.patch('bpython.autocomplete.glob', new=lambda text: ['abcde', 'aaaaa']) + @mock.patch('bpython.autocomplete.glob', + new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: False) @mock.patch('os.path.sep', new='/') @@ -127,7 +134,8 @@ def test_match_returns_files_when_files_exist(self): self.assertEqual(sorted(self.completer.matches(2, '"x')), ['aaaaa', 'abcde']) - @mock.patch('bpython.autocomplete.glob', new=lambda text: ['abcde', 'aaaaa']) + @mock.patch('bpython.autocomplete.glob', + new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: True) @mock.patch('os.path.sep', new='/') @@ -135,8 +143,10 @@ def test_match_returns_dirs_when_dirs_exist(self): self.assertEqual(sorted(self.completer.matches(2, '"x')), ['aaaaa/', 'abcde/']) - @mock.patch('bpython.autocomplete.glob', new=lambda text: ['/expand/ed/abcde', '/expand/ed/aaaaa']) - @mock.patch('os.path.expanduser', new=lambda text: text.replace('~', '/expand/ed')) + @mock.patch('bpython.autocomplete.glob', + new=lambda text: ['/expand/ed/abcde', '/expand/ed/aaaaa']) + @mock.patch('os.path.expanduser', + new=lambda text: text.replace('~', '/expand/ed')) @mock.patch('os.path.isdir', new=lambda text: False) @mock.patch('os.path.sep', new='/') def test_tilde_stays_pretty(self): @@ -148,12 +158,14 @@ def test_formatting_takes_just_last_part(self): self.assertEqual(self.completer.format('/hello/there/'), 'there/') self.assertEqual(self.completer.format('/hello/there'), 'there') + class MockNumPy(object): - """ - This is a mock numpy object that raises an error when there is an atempt to convert it to a boolean. - """ + """This is a mock numpy object that raises an error when there is an atempt + to convert it to a boolean.""" + def __nonzero__(self): - raise ValueError("The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()") + raise ValueError("The truth value of an array with more than one " + "element is ambiguous. Use a.any() or a.all()") class TestDictKeyCompletion(unittest.TestCase): @@ -172,20 +184,24 @@ def test_empty_set_returned_when_eval_error(self): def test_empty_set_returned_when_not_dict_type(self): com = autocomplete.DictKeyCompletion() local = {'l': ["ab", "cd"]} - self.assertSetEqual(com.matches(2, "l[", locals_=local),set()) + self.assertSetEqual(com.matches(2, "l[", locals_=local), set()) def test_obj_that_does_not_allow_conversion_to_bool(self): com = autocomplete.DictKeyCompletion() local = {'mNumPy': MockNumPy()} self.assertSetEqual(com.matches(7, "mNumPy[", locals_=local), set()) + class Foo(object): a = 10 + def __init__(self): self.b = 20 + def method(self, x): pass + class TestAttrCompletion(unittest.TestCase): def test_att_matches_found_on_instance(self): @@ -193,6 +209,7 @@ def test_att_matches_found_on_instance(self): self.assertSetEqual(com.matches(2, 'a.', locals_={'a': Foo()}), set(['a.method', 'a.a', 'a.b'])) + class TestMagicMethodCompletion(unittest.TestCase): def test_magic_methods_complete_after_double_underscores(self): @@ -201,8 +218,10 @@ def test_magic_methods_complete_after_double_underscores(self): self.assertSetEqual(com.matches(10, ' def __', current_block=block), set(autocomplete.MAGIC_METHODS)) + Comp = namedtuple('Completion', ['name', 'complete']) + class TestMultilineJediCompletion(unittest.TestCase): @unittest.skipIf(not has_jedi, "jedi not available") diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 8c94521f5..71a334e12 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -35,10 +35,12 @@ def attr(func, *args, **kwargs): TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") + def set_win_size(fd, rows, columns): s = struct.pack('HHHH', rows, columns, 0, 0) fcntl.ioctl(fd, termios.TIOCSWINSZ, s) + class CrashersTest(object): backend = "cli" @@ -88,9 +90,10 @@ def processExited(self, reason): (master, slave) = pty.openpty() set_win_size(slave, 25, 80) - reactor.spawnProcess(Protocol(), sys.executable, - (sys.executable, "-m", "bpython." + self.backend, - "--config", TEST_CONFIG), + reactor.spawnProcess( + Protocol(), sys.executable, + (sys.executable, "-m", "bpython." + self.backend, "--config", + TEST_CONFIG), env=dict(TERM="vt100", LANG=os.environ.get("LANG", "")), usePTY=(master, slave, os.ttyname(slave))) return result @@ -119,14 +122,17 @@ def spam(a, (b, c)): def check_no_traceback(self, data): self.assertNotIn("Traceback", data) + @unittest.skipIf(reactor is None, "twisted is not available") class CursesCrashersTest(TrialTestCase, CrashersTest): backend = "cli" + @unittest.skipIf(not have_urwid, "urwid is not available") @unittest.skipIf(reactor is None, "twisted is not available") class UrwidCrashersTest(TrialTestCase, CrashersTest): backend = "urwid" + if __name__ == "__main__": unittest.main() diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index 26ee371d3..472092380 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -7,6 +7,7 @@ from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput + class TestCodeRunner(unittest.TestCase): def setUp(self): @@ -18,7 +19,8 @@ def tearDown(self): sys.stderr = self.orig_stderr def test_simple(self): - c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or self.orig_stderr.flush()) + c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or + self.orig_stderr.flush()) stdout = FakeOutput(c, lambda *args, **kwargs: None) stderr = FakeOutput(c, lambda *args, **kwargs: None) sys.stdout = stdout @@ -29,9 +31,12 @@ def test_simple(self): c.run_code() def test_exception(self): - c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or self.orig_stderr.flush()) + c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or + self.orig_stderr.flush()) + def ctrlc(): raise KeyboardInterrupt() + stdout = FakeOutput(c, lambda x: ctrlc()) stderr = FakeOutput(c, lambda *args, **kwargs: None) sys.stdout = stdout diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 933531c9c..2f24a3d09 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -12,7 +12,8 @@ from bpython.curtsiesfrontend.repl import Repl from bpython.curtsiesfrontend import replpainter from bpython.repl import History -from bpython.curtsiesfrontend.repl import INCONSISTENT_HISTORY_MSG, CONTIGUITY_BROKEN_MSG +from bpython.curtsiesfrontend.repl import INCONSISTENT_HISTORY_MSG, \ + CONTIGUITY_BROKEN_MSG from bpython.test import FixLanguageTestCase as TestCase @@ -25,7 +26,8 @@ def setup_config(): class TestCurtsiesPainting(FormatStringTest, TestCase): def setUp(self): self.repl = Repl(config=setup_config()) - self.repl.rl_history = History() # clear history + # clear history + self.repl.rl_history = History() self.repl.height, self.repl.width = (5, 10) def assert_paint(self, screen, cursor_row_col): @@ -54,8 +56,9 @@ def test_startup(self): def test_enter_text(self): [self.repl.add_normal_character(c) for c in '1 + 1'] - screen = fsarray([cyan('>>> ') + bold(green('1')+cyan(' ')+ - yellow('+') + cyan(' ') + green('1')), cyan('Welcome to')]) + screen = fsarray([cyan('>>> ') + bold(green('1') + cyan(' ') + + yellow('+') + cyan(' ') + green('1')), + cyan('Welcome to')]) self.assert_paint(screen, (0, 9)) def test_run_line(self): @@ -93,16 +96,18 @@ def foo(x, y, z=10): pass argspec = inspection.getargspec('foo', foo) + [1] array = replpainter.formatted_argspec(argspec, 30, setup_config()) - screen = [(bold(cyan(u'foo'))+cyan(':')+cyan(' ')+cyan('(')+cyan('x') + - yellow(',')+yellow(' ')+bold(cyan('y'))+yellow(',') + - yellow(' ')+cyan('z')+yellow('=')+bold(cyan('10'))+yellow(')'))] + screen = [bold(cyan(u'foo')) + cyan(':') + cyan(' ') + cyan('(') + + cyan('x') + yellow(',') + yellow(' ') + bold(cyan('y')) + + yellow(',') + yellow(' ') + cyan('z') + yellow('=') + + bold(cyan('10')) + yellow(')')] self.assertFSArraysEqual(fsarray(array), fsarray(screen)) def test_formatted_docstring(self): actual = replpainter.formatted_docstring( 'Returns the results\n\n' 'Also has side effects', 40, config=setup_config()) - expected = fsarray(['Returns the results', '', 'Also has side effects']) + expected = fsarray(['Returns the results', '', + 'Also has side effects']) self.assertFSArraysEqualIgnoringFormatting(actual, expected) def test_paint_lasts_events(self): @@ -161,8 +166,10 @@ def undo(self): def setUp(self): self.refresh_requests = [] - self.repl = Repl(banner='', config=setup_config(), request_refresh=self.refresh) - self.repl.rl_history = History() # clear history + self.repl = Repl(banner='', config=setup_config(), + request_refresh=self.refresh) + # clear history + self.repl.rl_history = History() self.repl.height, self.repl.width = (5, 32) def test_rewind(self): @@ -206,7 +213,7 @@ def test_rewind_contiguity_loss(self): u'', u'', u'', - u' '] #TODO why is that there? Necessary? + u' '] # TODO why is that there? Necessary? self.assert_paint_ignoring_formatting(screen, (1, 4)) screen = [u'>>> '] self.assert_paint_ignoring_formatting(screen, (0, 4)) @@ -286,7 +293,9 @@ def test_rewind_inconsistent_history_more_lines_same_screen(self): self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], u'6', - u'>>> 1 + 1', # everything will jump down a line - that's perfectly reasonable + # everything will jump down a line - that's perfectly + # reasonable + u'>>> 1 + 1', u'2', u'>>> ', u' '] @@ -392,9 +401,11 @@ def test_rewind_history_not_quite_inconsistent(self): sys.a = 6 self.undo() screen = [u'5', - u'>>> 1 + 1', # everything will jump down a line - that's perfectly reasonable + # everything will jump down a line - that's perfectly + # reasonable + u'>>> 1 + 1', u'2', - u'>>> ',] + u'>>> '] self.assert_paint_ignoring_formatting(screen, (3, 4)) def test_rewind_barely_consistent(self): @@ -418,7 +429,6 @@ def test_rewind_barely_consistent(self): u'>>> '] self.assert_paint_ignoring_formatting(screen, (2, 4)) - def test_clear_screen(self): self.enter("1 + 1") self.enter("2 + 2") @@ -474,7 +484,8 @@ def test_clear_screen_while_banner_visible(self): self.assert_paint_ignoring_formatting(screen, (3, 4)) def test_cursor_stays_at_bottom_of_screen(self): - """infobox showing up during intermediate render was causing this to fail, #371""" + """infobox showing up during intermediate render was causing this to + fail, #371""" self.repl.width = 50 self.repl.current_line = "__import__('random').__name__" with output_to_repl(self.repl): @@ -525,7 +536,7 @@ def test_unhighlight_paren_bugs(self): def send_key(self, key): self.repl.process_event(u'' if key == ' ' else key) - self.repl.paint() # has some side effects we need to be wary of + self.repl.paint() # has some side effects we need to be wary of def test_472(self): [self.send_key(c) for c in "(1, 2, 3)"] diff --git a/bpython/test/test_curtsies_parser.py b/bpython/test/test_curtsies_parser.py index 8ff8b9f81..4b2c96e00 100644 --- a/bpython/test/test_curtsies_parser.py +++ b/bpython/test/test_curtsies_parser.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + try: import unittest2 as unittest except ImportError: @@ -6,14 +8,17 @@ from bpython.curtsiesfrontend import parse from curtsies.fmtfuncs import yellow, cyan, green, bold + class TestExecArgs(unittest.TestCase): def test_parse(self): - self.assertEquals(parse.parse(u'\x01y\x03print\x04'), - yellow(u'print')) + self.assertEquals(parse.parse('\x01y\x03print\x04'), yellow('print')) - self.assertEquals(parse.parse(u'\x01y\x03print\x04\x01c\x03 \x04\x01g\x031\x04\x01c\x03 \x04\x01Y\x03+\x04\x01c\x03 \x04\x01g\x032\x04'), - yellow(u'print')+cyan(u' ')+green(u'1')+cyan(u' ')+bold(yellow(u'+'))+cyan(' ')+green(u'2')) + self.assertEquals( + parse.parse('\x01y\x03print\x04\x01c\x03 \x04\x01g\x031\x04\x01c' + '\x03 \x04\x01Y\x03+\x04\x01c\x03 \x04\x01g\x032\x04'), + yellow('print') + cyan(' ') + green('1') + cyan(' ') + + bold(yellow('+')) + cyan(' ') + green(u'2')) def test_peal_off_string(self): self.assertEquals(parse.peel_off_string('\x01RI\x03]\x04asdf'), diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index f94ad22df..1a5c2f545 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -28,7 +28,7 @@ def setup_config(conf): config.loadini(config_struct, os.devnull) for key, value in conf.items(): if not hasattr(config_struct, key): - raise ValueError("%r is not a valid config attribute" % (key,)) + raise ValueError("%r is not a valid config attribute" % (key, )) setattr(config_struct, key, value) return config_struct @@ -39,7 +39,8 @@ def setUp(self): self.repl = create_repl() def cfwp(self, source): - return interpreter.code_finished_will_parse(source, self.repl.interp.compile) + return interpreter.code_finished_will_parse(source, + self.repl.interp.compile) def test_code_finished_will_parse(self): self.repl.buffer = ['1 + 1'] @@ -67,25 +68,26 @@ def test_external_communication_encoding(self): self.repl.send_session_to_external_editor() def test_get_last_word(self): - self.repl.rl_history.entries=['1','2 3','4 5 6'] + self.repl.rl_history.entries = ['1', '2 3', '4 5 6'] self.repl._set_current_line('abcde') self.repl.get_last_word() - self.assertEqual(self.repl.current_line,'abcde6') + self.assertEqual(self.repl.current_line, 'abcde6') self.repl.get_last_word() - self.assertEqual(self.repl.current_line,'abcde3') + self.assertEqual(self.repl.current_line, 'abcde3') - @unittest.skip # this is the behavior of bash - not currently implemented + # this is the behavior of bash - not currently implemented + @unittest.skip def test_get_last_word_with_prev_line(self): - self.repl.rl_history.entries=['1','2 3','4 5 6'] + self.repl.rl_history.entries = ['1', '2 3', '4 5 6'] self.repl._set_current_line('abcde') self.repl.up_one_line() - self.assertEqual(self.repl.current_line,'4 5 6') + self.assertEqual(self.repl.current_line, '4 5 6') self.repl.get_last_word() - self.assertEqual(self.repl.current_line,'4 5 63') + self.assertEqual(self.repl.current_line, '4 5 63') self.repl.get_last_word() - self.assertEqual(self.repl.current_line,'4 5 64') + self.assertEqual(self.repl.current_line, '4 5 64') self.repl.up_one_line() - self.assertEqual(self.repl.current_line,'2 3') + self.assertEqual(self.repl.current_line, '2 3') def mock_next(obj, return_value): @@ -94,13 +96,16 @@ def mock_next(obj, return_value): else: obj.next.return_value = return_value + class TestCurtsiesReplTab(TestCase): def setUp(self): self.repl = create_repl() self.repl.matches_iter = MagicIterMock() + def add_matches(*args, **kwargs): self.repl.matches_iter.matches = ['aaa', 'aab', 'aac'] + self.repl.complete = Mock(side_effect=add_matches, return_value=True) @@ -163,7 +168,7 @@ class TestCurtsiesReplFilenameCompletion(TestCase): def setUp(self): self.repl = create_repl() - def test_list_win_visible_and_match_selected_on_tab_when_multiple_options(self): + def test_list_win_visible_match_selected_on_tab_multiple_options(self): self.repl._current_line = " './'" self.repl._cursor_offset = 2 with patch('bpython.autocomplete.get_completer_bpython') as mock: @@ -201,7 +206,8 @@ def test_list_win_not_visible_and_match_selected_if_one_option(self): self.assertEqual(self.repl.list_win_visible, False) -@contextmanager # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy +# from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy +@contextmanager def captured_output(): new_out, new_err = StringIO(), StringIO() old_out, old_err = sys.stdout, sys.stderr @@ -211,6 +217,7 @@ def captured_output(): finally: sys.stdout, sys.stderr = old_out, old_err + def create_repl(**kwargs): config = setup_config({'editor': 'true'}) repl = curtsiesrepl.Repl(config=config, **kwargs) @@ -219,6 +226,7 @@ def create_repl(**kwargs): repl.height = 20 return repl + class TestFutureImports(TestCase): def test_repl(self): @@ -242,6 +250,7 @@ def test_interactive(self): self.assertEqual(out.getvalue(), '0.5\n0.5\n') + class TestPredictedIndent(TestCase): def setUp(self): self.repl = create_repl() @@ -255,8 +264,8 @@ def test_simple(self): @unittest.skip def test_complex(self): - self.assertEqual(self.repl.predicted_indent('[a,'), 1) - self.assertEqual(self.repl.predicted_indent('reduce(asdfasdf,'), 7) + self.assertEqual(self.repl.predicted_indent('[a, '), 1) + self.assertEqual(self.repl.predicted_indent('reduce(asdfasdf, '), 7) class TestCurtsiesReevaluate(TestCase): @@ -270,6 +279,7 @@ def test_variable_is_cleared(self): self.repl.undo() self.assertNotIn('b', self.repl.interp.locals) + class TestCurtsiesPagerText(TestCase): def setUp(self): @@ -296,5 +306,6 @@ def test_show_source_formatted(self): self.repl.get_source_of_current_name = lambda: 'source code å∂߃åß∂ƒ' self.repl.show_source() + if __name__ == '__main__': unittest.main() diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index 192768894..142251b62 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -13,6 +13,7 @@ except ImportError: has_watchdog = False + @unittest.skipIf(not has_watchdog, "watchdog not available") class TestModuleChangeEventHandler(unittest.TestCase): @@ -32,4 +33,3 @@ def test_activate_throws_error_when_already_activated(self): self.module.activated = True with self.assertRaises(ValueError): self.module.activate() - diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 823aa4f78..012ae52a5 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -10,18 +10,19 @@ class TestSimpleComplete(unittest.TestCase): def setUp(self): self.original_modules = importcompletion.modules - importcompletion.modules = ['zzabc', 'zzabd', 'zzefg', 'zzabc.e', 'zzabc.f'] + importcompletion.modules = ['zzabc', 'zzabd', 'zzefg', 'zzabc.e', + 'zzabc.f'] def tearDown(self): importcompletion.modules = self.original_modules def test_simple_completion(self): - self.assertEqual(sorted(importcompletion.complete(10, 'import zza')), - ['zzabc', 'zzabd']) + self.assertSetEqual(importcompletion.complete(10, 'import zza'), + set(['zzabc', 'zzabd'])) def test_package_completion(self): - self.assertEqual(sorted(importcompletion.complete(13, 'import zzabc.')), - ['zzabc.e', 'zzabc.f', ]) + self.assertSetEqual(importcompletion.complete(13, 'import zzabc.'), + set(['zzabc.e', 'zzabc.f'])) class TestRealComplete(unittest.TestCase): @@ -38,14 +39,14 @@ def tearDownClass(cls): importcompletion.modules = set() def test_from_attribute(self): - self.assertEqual(sorted(importcompletion.complete(19, 'from sys import arg')), - ['argv']) + self.assertSetEqual( + importcompletion.complete(19, 'from sys import arg'), + set(['argv'])) def test_from_attr_module(self): - self.assertEqual(sorted(importcompletion.complete(9, 'from os.p')), - ['os.path']) + self.assertSetEqual(importcompletion.complete(9, 'from os.p'), + set(['os.path'])) def test_from_package(self): - self.assertEqual(sorted(importcompletion.complete(17, 'from xml import d')), - ['dom']) - + self.assertSetEqual(importcompletion.complete(17, 'from xml import d'), + set(['dom'])) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 2280aba7c..83056c563 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + try: import unittest2 as unittest except ImportError: @@ -6,6 +8,7 @@ from bpython.curtsiesfrontend import interpreter from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain + class TestInterpreter(unittest.TestCase): def test_syntaxerror(self): i = interpreter.Interp() @@ -17,7 +20,9 @@ def append_to_a(message): i.write = append_to_a i.runsource('1.1.1.1') - expected = ''+u''+u' File '+green(u'""')+u', line '+bold(magenta(u'1'))+u'\n'+u' '+u'1.1'+u'.'+u'1.1'+u'\n'+u' '+u' '+u'^'+u'\n'+bold(red(u'SyntaxError'))+u': '+cyan(u'invalid syntax')+u'\n' + expected = ' File ' + green('""') + ', line ' + \ + bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + \ + bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n' self.assertEquals(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) @@ -39,8 +44,10 @@ def g(): i.runsource('g()') - expected = u'Traceback (most recent call last):\n'+''+u' File '+green(u'""')+u', line '+bold (magenta(u'1'))+u', in '+cyan(u'')+u'\n'+''+bold(red(u'NameError'))+u': '+cyan(u"name 'g' is not defined")+u'\n' + expected = 'Traceback (most recent call last):\n File ' + \ + green('""') + ', line ' + bold(magenta('1')) + ', in ' + \ + cyan('') + '\n' + bold(red('NameError')) + ': ' + \ + cyan("name 'g' is not defined") + '\n' self.assertEquals(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) - diff --git a/bpython/test/test_keys.py b/bpython/test/test_keys.py index 046d0fbfb..b7a45d0c2 100644 --- a/bpython/test/test_keys.py +++ b/bpython/test/test_keys.py @@ -5,6 +5,7 @@ import bpython.keys as keys + class TestCLIKeys(unittest.TestCase): def test_keymap_map(self): """Verify KeyMap.map being a dictionary with the correct @@ -13,7 +14,7 @@ def test_keymap_map(self): def test_keymap_setitem(self): """Verify keys.KeyMap correctly setting items.""" - keys.cli_key_dispatch['simon'] = 'awesome'; + keys.cli_key_dispatch['simon'] = 'awesome' self.assertEqual(keys.cli_key_dispatch['simon'], 'awesome') def test_keymap_delitem(self): @@ -34,7 +35,8 @@ def test_keymap_keyerror(self): def raiser(): keys.cli_key_dispatch['C-asdf'] keys.cli_key_dispatch['C-qwerty'] - self.assertRaises(KeyError, raiser); + self.assertRaises(KeyError, raiser) + class TestUrwidKeys(unittest.TestCase): def test_keymap_map(self): @@ -44,7 +46,7 @@ def test_keymap_map(self): def test_keymap_setitem(self): """Verify keys.KeyMap correctly setting items.""" - keys.urwid_key_dispatch['simon'] = 'awesome'; + keys.urwid_key_dispatch['simon'] = 'awesome' self.assertEqual(keys.urwid_key_dispatch['simon'], 'awesome') def test_keymap_delitem(self): @@ -65,7 +67,7 @@ def test_keymap_keyerror(self): def raiser(): keys.urwid_key_dispatch['C-asdf'] keys.urwid_key_dispatch['C-qwerty'] - self.assertRaises(KeyError, raiser); + self.assertRaises(KeyError, raiser) if __name__ == '__main__': diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 93bf6aa2f..2f83d7175 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -6,10 +6,10 @@ import re from bpython.line import current_word, current_dict_key, current_dict, \ - current_string, current_object, current_object_attribute, \ - current_from_import_from, current_from_import_import, current_import, \ - current_method_definition_name, current_single_word, \ - current_string_literal_attr + current_string, current_object, current_object_attribute, \ + current_from_import_from, current_from_import_import, current_import, \ + current_method_definition_name, current_single_word, \ + current_string_literal_attr def cursor(s): @@ -18,12 +18,13 @@ def cursor(s): line = s[:cursor_offset] + s[cursor_offset+1:] return cursor_offset, line + def decode(s): """'ad' -> ((3, 'abcd'), (1, 3, 'bdc'))""" if not s.count('|') == 1: raise ValueError('match helper needs | to occur once') - if not ((s.count('<') == s.count('>') == 1 or s.count('<') == s.count('>') == 0)): + if s.count('<') != s.count('>') or not s.count('<') in (0, 1): raise ValueError('match helper needs <, and > to occur just once') matches = list(re.finditer(r'[<>|]', s)) assert len(matches) in [1, 3], [m.group() for m in matches] @@ -31,16 +32,18 @@ def decode(s): for i, m in enumerate(matches): d[m.group(0)] = m.start() - i s = s[:m.start() - i] + s[m.end() - i:] - assert len(d) in [1,3], 'need all the parts just once! %r' % d + assert len(d) in [1, 3], 'need all the parts just once! %r' % d if '<' in d: return (d['|'], s), (d['<'], d['>'], s[d['<']:d['>']]) else: return (d['|'], s), None + def line_with_cursor(cursor_offset, line): return line[:cursor_offset] + '|' + line[cursor_offset:] + def encode(cursor_offset, line, result): """encode(3, 'abdcd', (1, 3, 'bdc')) -> ad' @@ -76,7 +79,12 @@ def assertAccess(self, s): (cursor_offset, line), match = decode(s) result = self.func(cursor_offset, line) - self.assertEqual(result, match, "%s(%r) result\n%r (%r) doesn't match expected\n%r (%r)" % (self.func.__name__, line_with_cursor(cursor_offset, line), encode(cursor_offset, line, result), result, s, match)) + self.assertEqual( + result, match, + "%s(%r) result\n%r (%r) doesn't match expected\n%r (%r)" % ( + self.func.__name__, line_with_cursor(cursor_offset, line), + encode(cursor_offset, line, result), result, s, match)) + class TestHelpers(LineTestCase): def test_I(self): @@ -99,6 +107,7 @@ def dumb_func(cursor_offset, line): self.func = dumb_func self.assertAccess('d') + class TestCurrentWord(LineTestCase): def setUp(self): self.func = current_word @@ -128,9 +137,11 @@ def test_open_paren(self): self.assertAccess('') # documenting current behavior - TODO is this intended? + class TestCurrentDictKey(LineTestCase): def setUp(self): self.func = current_dict_key + def test_simple(self): self.assertAccess('asdf|') self.assertAccess('asdf|') @@ -143,12 +154,14 @@ def test_simple(self): self.assertAccess('asdf[<(1,>|]') self.assertAccess('asdf[<(1, >|]') self.assertAccess('asdf[<(1, 2)>|]') - #TODO self.assertAccess('d[d[<12|>') + # TODO self.assertAccess('d[d[<12|>') self.assertAccess("d[<'a>|") + class TestCurrentDict(LineTestCase): def setUp(self): self.func = current_dict + def test_simple(self): self.assertAccess('asdf|') self.assertAccess('asdf|') @@ -157,9 +170,11 @@ def test_simple(self): self.assertAccess('[abc|') self.assertAccess('asdf|') + class TestCurrentString(LineTestCase): def setUp(self): self.func = current_string + def test_closed(self): self.assertAccess('""') self.assertAccess('""') @@ -169,6 +184,7 @@ def test_closed(self): self.assertAccess("''''''") self.assertAccess('""""""') self.assertAccess('asdf.afd("a") + ""') + def test_open(self): self.assertAccess('"') self.assertAccess('"') @@ -179,9 +195,11 @@ def test_open(self): self.assertAccess('"""') self.assertAccess('asdf.afd("a") + "') + class TestCurrentObject(LineTestCase): def setUp(self): self.func = current_object + def test_simple(self): self.assertAccess('.attr1|') self.assertAccess('.|') @@ -194,9 +212,11 @@ def test_simple(self): self.assertAccess('stuff[asd|fg]') self.assertAccess('stuff[asdf[asd|fg]') + class TestCurrentAttribute(LineTestCase): def setUp(self): self.func = current_object_attribute + def test_simple(self): self.assertAccess('Object.') self.assertAccess('Object.attr1.') @@ -207,9 +227,11 @@ def test_simple(self): self.assertAccess('Object.attr1.<|attr2>') self.assertAccess('Object..attr2') + class TestCurrentFromImportFrom(LineTestCase): def setUp(self): self.func = current_from_import_from + def test_simple(self): self.assertAccess('from import path') self.assertAccess('from import path|') @@ -222,9 +244,11 @@ def test_simple(self): self.assertAccess('if True: from import sep|') self.assertAccess('from ') + class TestCurrentFromImportImport(LineTestCase): def setUp(self): self.func = current_from_import_import + def test_simple(self): self.assertAccess('from sys import ') self.assertAccess('from sys import ') @@ -233,11 +257,14 @@ def test_simple(self): self.assertAccess('from s|ys import path') self.assertAccess('from |sys import path') self.assertAccess('from xml.dom import ') - self.assertAccess('from xml.dom import Node.as|d') # because syntax error + # because syntax error + self.assertAccess('from xml.dom import Node.as|d') + class TestCurrentImport(LineTestCase): def setUp(self): self.func = current_import + def test_simple(self): self.assertAccess('import ') self.assertAccess('import ') @@ -249,14 +276,17 @@ def test_simple(self): self.assertAccess('if True: import ') self.assertAccess('if True: import as something') + class TestMethodDefinitionName(LineTestCase): def setUp(self): self.func = current_method_definition_name + def test_simple(self): self.assertAccess('def ') self.assertAccess(' def bar(x, y)|:') self.assertAccess(' def (x, y)') + class TestSingleWord(LineTestCase): def setUp(self): self.func = current_single_word @@ -266,9 +296,11 @@ def test_simple(self): self.assertAccess('.foo|') self.assertAccess(' ') + class TestCurrentStringLiteral(LineTestCase): def setUp(self): self.func = current_string_literal_attr + def test_simple(self): self.assertAccess('"hey".') self.assertAccess('"hey"|') @@ -277,5 +309,6 @@ def test_simple(self): self.assertAccess('"hey".asdf d|') self.assertAccess('"hey".<|>') + if __name__ == '__main__': unittest.main() diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 91a787eaf..c286905b2 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -56,7 +56,6 @@ def test_end_of_line(self): def test_forward_word(self): line = "going from here to_here" - #012345678901234567890123 start_pos = 11 next_word_pos = 15 expected = (next_word_pos, line) @@ -70,7 +69,6 @@ def test_forward_word(self): def test_forward_word_tabs(self): line = "going from here to_here" - #01234567890123456789012345678 start_pos = 11 next_word_pos = 15 expected = (next_word_pos, line) @@ -84,7 +82,6 @@ def test_forward_word_tabs(self): def test_forward_word_end(self): line = "going from here to_here" - #012345678901234567890123 start_pos = 16 next_word_pos = 23 expected = (next_word_pos, line) @@ -103,7 +100,6 @@ def test_forward_word_end(self): def test_forward_word_empty(self): line = "" - #0 start_pos = 0 next_word_pos = 0 expected = (next_word_pos, line) @@ -154,25 +150,28 @@ def test_delete_from_cursor_forward(self): self.assertEquals(delete_from_cursor_forward(0, ''), (0, '', '')) def test_delete_rest_of_word(self): - self.try_stages_kill(['z|s;df asdf d s;a;a', - 'z|;df asdf d s;a;a', - 'z| asdf d s;a;a', - 'z| d s;a;a', - 'z| s;a;a', - 'z|;a;a', - 'z|;a', - 'z|', - 'z|'], delete_rest_of_word) + self.try_stages_kill([ + 'z|s;df asdf d s;a;a', + 'z|;df asdf d s;a;a', + 'z| asdf d s;a;a', + 'z| d s;a;a', + 'z| s;a;a', + 'z|;a;a', + 'z|;a', + 'z|', + 'z|'], + delete_rest_of_word) def test_delete_word_to_cursor(self): self.try_stages_kill([ - ' a;d sdf ;a;s;d; fjksald|a', - ' a;d sdf ;a;s;d; |a', - ' a;d sdf |a', - ' a;d |a', - ' |a', - '|a', - '|a'], delete_word_to_cursor) + ' a;d sdf ;a;s;d; fjksald|a', + ' a;d sdf ;a;s;d; |a', + ' a;d sdf |a', + ' a;d |a', + ' |a', + '|a', + '|a'], + delete_word_to_cursor) def test_yank_prev_killed_text(self): pass @@ -185,7 +184,8 @@ def try_stages(self, strings, func): raise ValueError("Need to use '|' to specify cursor") stages = [(s.index('|'), s.replace('|', '')) for s in strings] - for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], stages[1:]): + for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], + stages[1:]): self.assertEquals(func(initial_pos, initial), (final_pos, final)) def try_stages_kill(self, strings, func): @@ -193,9 +193,10 @@ def try_stages_kill(self, strings, func): raise ValueError("Need to use '|' to specify cursor") stages = [(s.index('|'), s.replace('|', '')) for s in strings] - for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], stages[1:]): - self.assertEquals(func(initial_pos, initial)[:-1], (final_pos, final)) - + for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], + stages[1:]): + self.assertEquals(func(initial_pos, initial)[:-1], + (final_pos, final)) def test_transpose_character_before_cursor(self): self.try_stages(["as|df asdf", @@ -213,16 +214,18 @@ def test_backspace(self): def test_delete_word_from_cursor_back(self): self.try_stages_kill([ - "asd;fljk asd;lfjas;dlkfj asdlk jasdf;ljk|", - "asd;fljk asd;lfjas;dlkfj asdlk jasdf;|", - "asd;fljk asd;lfjas;dlkfj asdlk |", - "asd;fljk asd;lfjas;dlkfj |", - "asd;fljk asd;lfjas;|", - "asd;fljk asd;|", - "asd;fljk |", - "asd;|", - "|", - "|"], delete_word_from_cursor_back) + "asd;fljk asd;lfjas;dlkfj asdlk jasdf;ljk|", + "asd;fljk asd;lfjas;dlkfj asdlk jasdf;|", + "asd;fljk asd;lfjas;dlkfj asdlk |", + "asd;fljk asd;lfjas;dlkfj |", + "asd;fljk asd;lfjas;|", + "asd;fljk asd;|", + "asd;fljk |", + "asd;|", + "|", + "|"], + delete_word_from_cursor_back) + class TestEdits(unittest.TestCase): @@ -256,7 +259,10 @@ def test_config(self): g = lambda cursor_offset, line: ('hey', 3) self.edits.add_config_attr('att', f) self.assertNotIn('att', self.edits) - class config: att = 'c' + + class config(object): + att = 'c' + key_dispatch = {'c': 'c'} configured_edits = self.edits.mapping_with_config(config, key_dispatch) self.assertTrue(configured_edits.__contains__, 'c') @@ -265,7 +271,8 @@ class config: att = 'c' configured_edits.add_config_attr, 'att2', g) self.assertRaises(NotImplementedError, configured_edits.add, 'd', g) - self.assertEqual(configured_edits.call('c', cursor_offset=5, line='asfd'), + self.assertEqual(configured_edits.call('c', cursor_offset=5, + line='asfd'), ('hi', 2)) diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index b5e337ad2..a81513b56 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -24,9 +24,11 @@ def get_fodder_source(test_name): for module in [original, processed]] if not orig: - raise ValueError("Can't locate test %s in original fodder file" % (test_name,)) + raise ValueError("Can't locate test %s in original fodder file" % + (test_name,)) if not xformed: - raise ValueError("Can't locate test %s in processed fodder file" % (test_name,)) + raise ValueError("Can't locate test %s in processed fodder file" % + (test_name,)) return orig.group(1), xformed.group(1) @@ -78,11 +80,13 @@ def test_empty_line_within_class(self): def test_blank_lines_in_for_loop(self): self.assertIndented('blank_lines_in_for_loop') - @skip("More advanced technique required: need to try compiling and backtracking") + @skip("More advanced technique required: need to try compiling and " + "backtracking") def test_blank_line_in_try_catch(self): self.assertIndented('blank_line_in_try_catch') - @skip("More advanced technique required: need to try compiling and backtracking") + @skip("More advanced technique required: need to try compiling and " + "backtracking") def test_blank_line_in_try_catch_else(self): self.assertIndented('blank_line_in_try_catch_else') diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index e13f36e2e..84c7e2a1d 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -2,6 +2,7 @@ from itertools import islice import os import socket +from six.moves import range from mock import Mock @@ -22,6 +23,7 @@ def setup_config(conf): config_struct.autocomplete_mode = conf['autocomplete_mode'] return config_struct + class FakeHistory(repl.History): def __init__(self): @@ -30,18 +32,21 @@ def __init__(self): def reset(self): pass + class FakeRepl(repl.Repl): def __init__(self, conf={}): repl.Repl.__init__(self, repl.Interpreter(), setup_config(conf)) self.current_line = "" self.cursor_offset = 0 + class FakeCliRepl(cli.CLIRepl, FakeRepl): def __init__(self): self.s = '' self.cpos = 0 self.rl_history = FakeHistory() + class TestMatchesIterator(unittest.TestCase): def setUp(self): @@ -104,9 +109,10 @@ def test_update(self): def test_cur_line(self): completer = Mock() - completer.locate.return_value = (0, - self.matches_iterator.orig_cursor_offset, - self.matches_iterator.orig_line) + completer.locate.return_value = ( + 0, + self.matches_iterator.orig_cursor_offset, + self.matches_iterator.orig_line) self.matches_iterator.completer = completer self.assertRaises(ValueError, self.matches_iterator.cur_line) @@ -196,7 +202,8 @@ def set_input_line(self, line): def assert_get_source_error_for_current_function(self, func, msg): self.repl.current_func = func - self.assertRaises(repl.SourceNotFound, self.repl.get_source_of_current_name) + self.assertRaises(repl.SourceNotFound, + self.repl.get_source_of_current_name) try: self.repl.get_source_of_current_name() except repl.SourceNotFound as e: @@ -205,26 +212,27 @@ def assert_get_source_error_for_current_function(self, func, msg): def test_current_function(self): self.set_input_line('INPUTLINE') self.repl.current_func = collections.MutableSet.add - self.assertIn("Add an element.", self.repl.get_source_of_current_name()) + self.assertIn("Add an element.", + self.repl.get_source_of_current_name()) self.assert_get_source_error_for_current_function( - collections.defaultdict.copy, "No source code found for INPUTLINE") + collections.defaultdict.copy, "No source code found for INPUTLINE") self.assert_get_source_error_for_current_function( - collections.defaultdict, "could not find class definition") + collections.defaultdict, "could not find class definition") self.assert_get_source_error_for_current_function( - [], "No source code found for INPUTLINE") + [], "No source code found for INPUTLINE") self.assert_get_source_error_for_current_function( - list.pop, "No source code found for INPUTLINE") + list.pop, "No source code found for INPUTLINE") def test_current_line(self): self.repl.interp.locals['a'] = socket.socket self.set_input_line('a') self.assertIn('dup(self)', self.repl.get_source_of_current_name()) -#TODO add tests for various failures without using current function +# TODO add tests for various failures without using current function class TestRepl(unittest.TestCase): @@ -239,7 +247,8 @@ def setUp(self): def test_current_string(self): self.setInputLine('a = "2"') - self.repl.cpos = 0 #TODO factor cpos out of repl.Repl + # TODO factor cpos out of repl.Repl + self.repl.cpos = 0 self.assertEqual(self.repl.current_string(), '"2"') self.setInputLine('a = "2" + 2') @@ -259,7 +268,8 @@ def test_simple_global_complete(self): self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, - ['def', 'del', 'delattr(', 'dict(', 'dir(', 'divmod(']) + ['def', 'del', 'delattr(', 'dict(', 'dir(', + 'divmod(']) @unittest.skip("disabled while non-simple completion is disabled") def test_substring_global_complete(self): @@ -267,9 +277,9 @@ def test_substring_global_complete(self): self.setInputLine("time") self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.completer,'matches')) + self.assertTrue(hasattr(self.repl.completer, 'matches')) self.assertEqual(self.repl.completer.matches, - ['RuntimeError(', 'RuntimeWarning(']) + ['RuntimeError(', 'RuntimeWarning(']) @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_global_complete(self): @@ -277,9 +287,9 @@ def test_fuzzy_global_complete(self): self.setInputLine("doc") self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.completer,'matches')) + self.assertTrue(hasattr(self.repl.completer, 'matches')) self.assertEqual(self.repl.completer.matches, - ['UnboundLocalError(', '__doc__']) + ['UnboundLocalError(', '__doc__']) # 2. Attribute tests def test_simple_attribute_complete(self): @@ -291,9 +301,8 @@ def test_simple_attribute_complete(self): self.repl.push(line) self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter,'matches')) - self.assertEqual(self.repl.matches_iter.matches, - ['Foo.bar']) + self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) + self.assertEqual(self.repl.matches_iter.matches, ['Foo.bar']) @unittest.skip("disabled while non-simple completion is disabled") def test_substring_attribute_complete(self): @@ -305,9 +314,8 @@ def test_substring_attribute_complete(self): self.repl.push(line) self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.completer,'matches')) - self.assertEqual(self.repl.completer.matches, - ['Foo.baz']) + self.assertTrue(hasattr(self.repl.completer, 'matches')) + self.assertEqual(self.repl.completer.matches, ['Foo.baz']) @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_attribute_complete(self): @@ -319,9 +327,8 @@ def test_fuzzy_attribute_complete(self): self.repl.push(line) self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.completer,'matches')) - self.assertEqual(self.repl.completer.matches, - ['Foo.bar']) + self.assertTrue(hasattr(self.repl.completer, 'matches')) + self.assertEqual(self.repl.completer.matches, ['Foo.bar']) # 3. Edge Cases def test_updating_namespace_complete(self): @@ -330,15 +337,14 @@ def test_updating_namespace_complete(self): self.repl.push("foobar = 2") self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter,'matches')) - self.assertEqual(self.repl.matches_iter.matches, - ['foobar']) + self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) + self.assertEqual(self.repl.matches_iter.matches, ['foobar']) def test_file_should_not_appear_in_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) self.setInputLine("_") self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter,'matches')) + self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertNotIn('__file__', self.repl.matches_iter.matches) @@ -367,6 +373,7 @@ def test_addstr(self): self.repl.addstr('buzz') self.assertEqual(self.repl.s, "foobuzzbar") + class TestCliReplTab(unittest.TestCase): def setUp(self): diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index 436714f2c..26c504075 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -21,6 +21,7 @@ def _(message): def ngettext(singular, plural, n): return translator.ungettext(singular, plural, n) + def init(locale_dir=None, languages=None): try: locale.setlocale(locale.LC_ALL, '') @@ -37,4 +38,3 @@ def init(locale_dir=None, languages=None): translator = gettext.translation('bpython', locale_dir, languages, fallback=True) - From 2b479b4252b474465c828fe69c3418cc086bf29d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 Feb 2015 14:05:30 +0100 Subject: [PATCH 0382/1650] Fix Python 3 compatibility issues Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 3 ++- bpython/urwid.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a79ecda25..fe7bb27f3 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -27,6 +27,7 @@ import os import rlcompleter from six.moves import range, builtins +from six import string_types from glob import glob @@ -312,7 +313,7 @@ def matches(self, cursor_offset, line, **kwargs): start, end, word = r if argspec: matches = set(name + '=' for name in argspec[1][0] - if isinstance(name, basestring) and + if isinstance(name, string_types) and name.startswith(word)) if py3: matches.update(name + '=' for name in argspec[1][4] diff --git a/bpython/urwid.py b/bpython/urwid.py index 995d8c52c..60390ace5 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -43,7 +43,7 @@ from types import ModuleType from optparse import Option from six.moves import range -from six import iteritems +from six import iteritems, string_types from pygments.token import Token @@ -285,7 +285,7 @@ def decoding_input_filter(keys, raw): encoding = locale.getpreferredencoding() converted_keys = list() for key in keys: - if isinstance(key, basestring): + if isinstance(key, string_types): converted_keys.append(key.decode(encoding)) else: converted_keys.append(key) From 331032516610e65d9fd478d044ccfb35b4be0ee1 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 6 Feb 2015 14:49:04 -0500 Subject: [PATCH 0383/1650] Fix #468 and #472 highlight paren bug --- bpython/repl.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index b70f783e2..e6ce23acc 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -933,10 +933,12 @@ def tokenize(self, s, newline=False): - reads self.cpos to see what parens should be highlighted - reads self.buffer to see what came before the passed in line - sets self.highlighted_paren to (buffer_lineno, tokens_for_that_line) - for buffer line that should replace that line to unhighlight it + for buffer line that should replace that line to unhighlight it, + or None if no paren is currently highlighted - calls reprint_line with a buffer's line's tokens and the buffer - lineno that has changed iff that line is the not the current line + lineno that has changed if line other than the current line changes """ + highlighted_paren = None source = '\n'.join(self.buffer + [s]) cursor = len(source) - self.cpos @@ -1007,16 +1009,17 @@ def tokenize(self, s, newline=False): line_tokens[-1] = (Parenthesis, value) (lineno, i, tokens, opening) = opening if lineno == len(self.buffer): - self.highlighted_paren = (lineno, saved_tokens) + highlighted_paren = (lineno, saved_tokens) line_tokens[i] = (Parenthesis, opening) else: - self.highlighted_paren = (lineno, list(tokens)) + highlighted_paren = (lineno, list(tokens)) # We need to redraw a line tokens[i] = (Parenthesis, opening) self.reprint_line(lineno, tokens) search_for_paren = False elif under_cursor: search_for_paren = False + self.highlighted_paren = highlighted_paren if line != len(self.buffer): return list() return line_tokens From 758d6a6b0a79776c0b3b901a9be84688a9d1b3f5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 Feb 2015 18:51:52 +0100 Subject: [PATCH 0384/1650] Use unittest.mock if possible Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 4 +++- bpython/test/__init__.py | 11 +++++---- bpython/test/test_autocomplete.py | 5 +++-- bpython/test/test_curtsies_repl.py | 23 +++++++++---------- bpython/test/test_filewatch.py | 2 +- bpython/test/test_repl.py | 36 ++++++++++++++---------------- setup.py | 5 ++++- 7 files changed, 46 insertions(+), 40 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index e59608a5a..c03b969c7 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -17,10 +17,12 @@ if [[ $RUN == nosetests ]]; then if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2 fi - # dependencies for crasher tests case $TRAVIS_PYTHON_VERSION in 2*) + # dependencies for crasher tests pip install Twisted urwid + # test specific dependencies + pip install mock ;; esac # build and install diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index b7960f912..94f33e6af 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -5,7 +5,10 @@ except ImportError: import unittest -from mock import MagicMock, Mock +try: + from unittest import mock +except ImportError: + import mock from bpython.translations import init from bpython._py3compat import py3 @@ -18,9 +21,9 @@ def setUpClass(cls): init(languages=['en']) -class MagicIterMock(MagicMock): +class MagicIterMock(mock.MagicMock): if py3: - __next__ = Mock(return_value=None) + __next__ = mock.Mock(return_value=None) else: - next = Mock(return_value=None) + next = mock.Mock(return_value=None) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 87f0dbbe7..dcf04bb6a 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -1,7 +1,5 @@ from collections import namedtuple -from bpython import autocomplete -import mock try: import unittest2 as unittest except ImportError: @@ -13,6 +11,9 @@ except ImportError: has_jedi = False +from bpython import autocomplete +from bpython.test import mock + class TestSafeEval(unittest.TestCase): def test_catches_syntax_error(self): diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 1a5c2f545..d723ed81d 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -6,7 +6,6 @@ import sys import tempfile from contextlib import contextmanager -from mock import Mock, patch from six.moves import StringIO try: @@ -20,7 +19,7 @@ from bpython import config from bpython import args from bpython._py3compat import py3 -from bpython.test import FixLanguageTestCase as TestCase, MagicIterMock +from bpython.test import FixLanguageTestCase as TestCase, MagicIterMock, mock def setup_config(conf): @@ -106,8 +105,8 @@ def setUp(self): def add_matches(*args, **kwargs): self.repl.matches_iter.matches = ['aaa', 'aab', 'aac'] - self.repl.complete = Mock(side_effect=add_matches, - return_value=True) + self.repl.complete = mock.Mock(side_effect=add_matches, + return_value=True) def test_tab_with_no_matches_triggers_completion(self): self.repl._current_line = ' asdf' @@ -171,9 +170,9 @@ def setUp(self): def test_list_win_visible_match_selected_on_tab_multiple_options(self): self.repl._current_line = " './'" self.repl._cursor_offset = 2 - with patch('bpython.autocomplete.get_completer_bpython') as mock: - mock.return_value = (['./abc', './abcd', './bcd'], - autocomplete.FilenameCompletion()) + with mock.patch('bpython.autocomplete.get_completer_bpython') as m: + m.return_value = (['./abc', './abcd', './bcd'], + autocomplete.FilenameCompletion()) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() @@ -183,9 +182,9 @@ def test_list_win_visible_match_selected_on_tab_multiple_options(self): def test_list_win_not_visible_and_cseq_if_cseq(self): self.repl._current_line = " './a'" self.repl._cursor_offset = 5 - with patch('bpython.autocomplete.get_completer_bpython') as mock: - mock.return_value = (['./abcd', './abce'], - autocomplete.FilenameCompletion()) + with mock.patch('bpython.autocomplete.get_completer_bpython') as m: + m.return_value = (['./abcd', './abce'], + autocomplete.FilenameCompletion()) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() @@ -196,8 +195,8 @@ def test_list_win_not_visible_and_cseq_if_cseq(self): def test_list_win_not_visible_and_match_selected_if_one_option(self): self.repl._current_line = " './a'" self.repl._cursor_offset = 5 - with patch('bpython.autocomplete.get_completer_bpython') as mock: - mock.return_value = (['./abcd'], autocomplete.FilenameCompletion()) + with mock.patch('bpython.autocomplete.get_completer_bpython') as m: + m.return_value = (['./abcd'], autocomplete.FilenameCompletion()) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index 142251b62..186781376 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -1,4 +1,3 @@ -import mock import os try: @@ -13,6 +12,7 @@ except ImportError: has_watchdog = False +from bpython.test import mock @unittest.skipIf(not has_watchdog, "watchdog not available") class TestModuleChangeEventHandler(unittest.TestCase): diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 84c7e2a1d..14699a0c2 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -4,8 +4,6 @@ import socket from six.moves import range -from mock import Mock - try: import unittest2 as unittest except ImportError: @@ -13,7 +11,7 @@ from bpython._py3compat import py3 from bpython import config, repl, cli, autocomplete -from bpython.test import MagicIterMock +from bpython.test import MagicIterMock, mock def setup_config(conf): @@ -99,7 +97,7 @@ def test_update(self): self.assertEqual(list(slice), self.matches) newmatches = ['string', 'str', 'set'] - completer = Mock() + completer = mock.Mock() completer.locate.return_value = (0, 1, 's') self.matches_iterator.update(1, 's', newmatches, completer) @@ -108,7 +106,7 @@ def test_update(self): self.assertEqual(list(newslice), newmatches) def test_cur_line(self): - completer = Mock() + completer = mock.Mock() completer.locate.return_value = ( 0, self.matches_iterator.orig_cursor_offset, @@ -363,7 +361,7 @@ def test_atbol(self): self.assertFalse(self.repl.atbol()) def test_addstr(self): - self.repl.complete = Mock(True) + self.repl.complete = mock.Mock(True) self.repl.s = "foo" self.repl.addstr("bar") @@ -386,11 +384,11 @@ def test_simple_tab_complete(self): self.repl.matches_iter.__bool__.return_value = False else: self.repl.matches_iter.__nonzero__.return_value = False - self.repl.complete = Mock() - self.repl.print_line = Mock() + self.repl.complete = mock.Mock() + self.repl.print_line = mock.Mock() self.repl.matches_iter.is_cseq.return_value = False - self.repl.show_list = Mock() - self.repl.argspec = Mock() + self.repl.show_list = mock.Mock() + self.repl.argspec = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "foobar") self.repl.s = "foo" @@ -420,22 +418,22 @@ def test_normal_tab(self): """make sure pressing the tab key will still in some cases add a tab""" self.repl.s = "" - self.repl.config = Mock() + self.repl.config = mock.Mock() self.repl.config.tab_length = 4 - self.repl.complete = Mock() - self.repl.print_line = Mock() + self.repl.complete = mock.Mock() + self.repl.print_line = mock.Mock() self.repl.tab() self.assertEqual(self.repl.s, " ") def test_back_parameter(self): - self.repl.matches_iter = Mock() + self.repl.matches_iter = mock.Mock() self.repl.matches_iter.matches = True self.repl.matches_iter.previous.return_value = "previtem" self.repl.matches_iter.is_cseq.return_value = False - self.repl.show_list = Mock() - self.repl.argspec = Mock() + self.repl.show_list = mock.Mock() + self.repl.argspec = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "previtem") - self.repl.print_line = Mock() + self.repl.print_line = mock.Mock() self.repl.s = "foo" self.repl.cpos = 0 self.repl.tab(back=True) @@ -465,10 +463,10 @@ def test_fuzzy_attribute_tab_complete2(self): def test_simple_expand(self): self.repl.s = "f" self.cpos = 0 - self.repl.matches_iter = Mock() + self.repl.matches_iter = mock.Mock() self.repl.matches_iter.is_cseq.return_value = True self.repl.matches_iter.substitute_cseq.return_value = (3, "foo") - self.repl.print_line = Mock() + self.repl.print_line = mock.Mock() self.repl.tab() self.assertEqual(self.repl.s, "foo") diff --git a/setup.py b/setup.py index 98e281d38..c4d3acccf 100755 --- a/setup.py +++ b/setup.py @@ -204,9 +204,12 @@ def initialize_options(self): install_requires.append('ndg-httpsclient') install_requires.append('pyasn1') -tests_require = ['mock'] +tests_require = [] if sys.version_info[0] == 2 and sys.version_info[1] < 7: tests_require.append('unittest2') +if (sys.version_info[0] == 2 or + (sys.version_info[0] == 3 and sys.version_info[0] < 3)): + tests_require.append('mock') # translations mo_files = list() From bb5c03edd688de302bafbe1776b5c090fa281fa1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 Feb 2015 19:07:56 +0100 Subject: [PATCH 0385/1650] Remove obsolete comment Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index e6ce23acc..67eb74a50 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -221,7 +221,6 @@ def next(self): return self.__next__() def __next__(self): - """Keep this around until we drop 2to3.""" self.index = (self.index + 1) % len(self.matches) return self.matches[self.index] From 935c849ff82b9fd86cfde9f1099e9c7f36f780a9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 Feb 2015 19:25:04 +0100 Subject: [PATCH 0386/1650] Remove duplicate code Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 7 ++++++- bpython/curtsiesfrontend/repl.py | 19 +++---------------- bpython/repl.py | 3 +-- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index cfb8cf2e4..72c5a1dd4 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1915,7 +1915,12 @@ def main_curses(scr, args, config, interactive=True, locals_=None, return (exit_value, clirepl.getstdout()) else: sys.path.insert(0, '') - clirepl.startup() + try: + clirepl.startup() + except OSError as e: + # Handle this with a proper error message. + if e.errno != errno.ENOENT: + raise if banner is not None: clirepl.write(banner) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 333a4b611..a05b89ec4 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -409,20 +409,6 @@ def sigtstp_handler(self, signum, frame): self.after_suspend() self.__enter__() - def run_startup(self): - """ - Execute PYTHONSTARTUP file if it exits. Call this after front - end-specific initialisation. - """ - filename = os.environ.get('PYTHONSTARTUP') - if filename: - with open(filename, 'r') as f: - if py3: - #TODO runsource has a new signature in PY3 - self.interp.runsource(f.read(), filename, 'exec') - else: - self.interp.runsource(f.read(), filename, 'exec') - def clean_up_current_line_for_exit(self): """Called when trying to exit to prep for final paint""" logger.debug('unhighlighting paren for exit') @@ -475,9 +461,10 @@ def process_control_event(self, e): elif isinstance(e, bpythonevents.RunStartupFileEvent): try: - self.run_startup() + self.startup() except IOError as e: - self.status_bar.message(_('Executing PYTHONSTARTUP failed: %s') % (str(e))) + self.status_bar.message( + _('Executing PYTHONSTARTUP failed: %s') % (str(e))) elif isinstance(e, bpythonevents.UndoEvent): self.undo(n=e.n) diff --git a/bpython/repl.py b/bpython/repl.py index 67eb74a50..8160419b7 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -19,7 +19,6 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# from __future__ import with_statement import code @@ -406,7 +405,7 @@ def startup(self): end-specific initialisation. """ filename = os.environ.get('PYTHONSTARTUP') - if filename and os.path.isfile(filename): + if filename: with open(filename, 'r') as f: if py3: self.interp.runsource(f.read(), filename, 'exec') From f72f39595f59732c613228ac49675d501102a8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Fri, 6 Feb 2015 23:48:34 +0100 Subject: [PATCH 0387/1650] Simplify test skipping if joda is not available. --- bpython/test/test_autocomplete.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index dcf04bb6a..6c0b39092 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -223,15 +223,14 @@ def test_magic_methods_complete_after_double_underscores(self): Comp = namedtuple('Completion', ['name', 'complete']) +@unittest.skipUnless(has_jedi, "jedi required") class TestMultilineJediCompletion(unittest.TestCase): - @unittest.skipIf(not has_jedi, "jedi not available") def test_returns_none_with_single_line(self): com = autocomplete.MultilineJediCompletion() self.assertEqual(com.matches(2, 'Va', current_block='Va', history=[]), None) - @unittest.skipIf(not has_jedi, "jedi not available") def test_returns_non_with_blank_second_line(self): com = autocomplete.MultilineJediCompletion() self.assertEqual(com.matches(0, '', current_block='class Foo():\n', @@ -246,14 +245,12 @@ def matches_from_completions(self, cursor, line, block, history, return com.matches(cursor, line, current_block=block, history=history) - @unittest.skipIf(not has_jedi, "jedi not available") def test_completions_starting_with_different_letters(self): matches = self.matches_from_completions( 2, ' a', 'class Foo:\n a', ['adsf'], [Comp('Abc', 'bc'), Comp('Cbc', 'bc')]) self.assertEqual(matches, None) - @unittest.skipIf(not has_jedi, "jedi not available") def test_completions_starting_with_different_cases(self): matches = self.matches_from_completions( 2, ' a', 'class Foo:\n a', ['adsf'], From 94bb6571f7f72a73000b988cd4d814a77e0ca200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Sat, 7 Feb 2015 00:10:05 +0100 Subject: [PATCH 0388/1650] =?UTF-8?q?Replace=20some=20'skipIf(not=20?= =?UTF-8?q?=E2=80=A6)'=20by=20'skipUnless'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bpython/test/test_crashers.py | 2 +- bpython/test/test_curtsies_repl.py | 12 ++++++------ bpython/test/test_filewatch.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 71a334e12..d07195929 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -128,7 +128,7 @@ class CursesCrashersTest(TrialTestCase, CrashersTest): backend = "cli" -@unittest.skipIf(not have_urwid, "urwid is not available") +@unittest.skipUnless(have_urwid, "urwid is required") @unittest.skipIf(reactor is None, "twisted is not available") class UrwidCrashersTest(TrialTestCase, CrashersTest): backend = "urwid" diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index d723ed81d..b201409ba 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -59,8 +59,8 @@ def test_external_communication(self): self.repl.send_current_block_to_external_editor() self.repl.send_session_to_external_editor() - @unittest.skipIf(not all(map(config.can_encode, 'å∂߃')), - 'Charset can not encode characters') + @unittest.skipUnless(all(map(config.can_encode, 'å∂߃')), + 'Charset can not encode characters') def test_external_communication_encoding(self): with captured_output(): self.repl.display_lines.append('>>> "åß∂ƒ"') @@ -291,15 +291,15 @@ def assert_pager_gets_unicode(self, text): def test_help(self): self.repl.pager(self.repl.help_text()) - @unittest.skipIf(not all(map(config.can_encode, 'å∂߃')), - 'Charset can not encode characters') + @unittest.skipUnless(all(map(config.can_encode, 'å∂߃')), + 'Charset can not encode characters') def test_show_source_not_formatted(self): self.repl.config.highlight_show_source = False self.repl.get_source_of_current_name = lambda: 'source code å∂߃åß∂ƒ' self.repl.show_source() - @unittest.skipIf(not all(map(config.can_encode, 'å∂߃')), - 'Charset can not encode characters') + @unittest.skipUnless(all(map(config.can_encode, 'å∂߃')), + 'Charset can not encode characters') def test_show_source_formatted(self): self.repl.config.highlight_show_source = True self.repl.get_source_of_current_name = lambda: 'source code å∂߃åß∂ƒ' diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index 186781376..5f94f40ec 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -14,7 +14,7 @@ from bpython.test import mock -@unittest.skipIf(not has_watchdog, "watchdog not available") +@unittest.skipUnless(has_watchdog, "watchdog required") class TestModuleChangeEventHandler(unittest.TestCase): def setUp(self): From 11d34d01d0abf98665c8fad7843553e523150ba9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 Feb 2015 23:41:45 +0100 Subject: [PATCH 0389/1650] PEP-8 Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/__init__.py | 1 - bpython/curtsiesfrontend/_internal.py | 1 + bpython/curtsiesfrontend/coderunner.py | 59 ++++++---- bpython/curtsiesfrontend/filewatch.py | 4 - bpython/curtsiesfrontend/interaction.py | 37 ++++--- bpython/curtsiesfrontend/interpreter.py | 64 +++++------ bpython/curtsiesfrontend/manual_readline.py | 108 +++++++++++++------ bpython/curtsiesfrontend/parse.py | 10 +- bpython/curtsiesfrontend/preprocess.py | 9 +- bpython/curtsiesfrontend/repl.py | 113 ++++++++++++-------- bpython/curtsiesfrontend/replpainter.py | 53 ++++++--- bpython/curtsiesfrontend/sitefix.py | 3 +- 12 files changed, 292 insertions(+), 170 deletions(-) diff --git a/bpython/curtsiesfrontend/__init__.py b/bpython/curtsiesfrontend/__init__.py index 8b1378917..e69de29bb 100644 --- a/bpython/curtsiesfrontend/__init__.py +++ b/bpython/curtsiesfrontend/__init__.py @@ -1 +0,0 @@ - diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 4fd166f07..501ce5da6 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -24,6 +24,7 @@ import bpython._internal + class _Helper(bpython._internal._Helper): def __init__(self, repl=None): diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index a15317b7b..7255e3d1e 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -1,12 +1,13 @@ -"""For running Python code that could interrupt itself at any time -in order to, for example, ask for a read on stdin, or a write on stdout +"""For running Python code that could interrupt itself at any time in order to, +for example, ask for a read on stdin, or a write on stdout -The CodeRunner spawns a greenlet to run code in, and that code can suspend -its own execution to ask the main greenlet to refresh the display or get information. +The CodeRunner spawns a greenlet to run code in, and that code can suspend its +own execution to ask the main greenlet to refresh the display or get +information. -Greenlets are basically threads that can explicitly switch control to each other. -You can replace the word "greenlet" with "thread" in these docs if that makes more -sense to you. +Greenlets are basically threads that can explicitly switch control to each +other. You can replace the word "greenlet" with "thread" in these docs if that +makes more sense to you. """ import code @@ -16,31 +17,40 @@ logger = logging.getLogger(__name__) + class SigintHappened(object): """If this class is returned, a SIGINT happened while the main greenlet""" + class SystemExitFromCodeGreenlet(SystemExit): - """If this class is returned, a SystemExit happened while in the code greenlet""" + """If this class is returned, a SystemExit happened while in the code + greenlet""" class RequestFromCodeGreenlet(object): """Message from the code greenlet""" + class Wait(RequestFromCodeGreenlet): """Running code would like the main loop to run for a bit""" + class Refresh(RequestFromCodeGreenlet): """Running code would like the main loop to refresh the display""" + class Done(RequestFromCodeGreenlet): """Running code is done running""" + class Unfinished(RequestFromCodeGreenlet): """Source code wasn't executed because it wasn't fully formed""" + class SystemExitRequest(RequestFromCodeGreenlet): """Running code raised a SystemExit""" + class CodeRunner(object): """Runs user code in an interpreter. @@ -67,31 +77,35 @@ class CodeRunner(object): just passes whatever is passed in to run_code(for_code) to the code greenlet """ - def __init__(self, interp=None, request_refresh=lambda:None): + def __init__(self, interp=None, request_refresh=lambda: None): """ interp is an interpreter object to use. By default a new one is created. - request_refresh is a function that will be called each time - the running code asks for a refresh - to, for example, update the screen. + request_refresh is a function that will be called each time the running + code asks for a refresh - to, for example, update the screen. """ self.interp = interp or code.InteractiveInterpreter() self.source = None self.main_greenlet = greenlet.getcurrent() self.code_greenlet = None self.request_refresh = request_refresh - self.code_is_waiting = False # waiting for response from main thread - self.sigint_happened_in_main_greenlet = False # sigint happened while in main thread + # waiting for response from main thread + self.code_is_waiting = False + # sigint happened while in main thread + self.sigint_happened_in_main_greenlet = False self.orig_sigint_handler = None @property def running(self): - """Returns greenlet if code has been loaded greenlet has been started""" + """Returns greenlet if code has been loaded greenlet has been + started""" return self.source and self.code_greenlet def load_code(self, source): """Prep code to be run""" - assert self.source is None, "you shouldn't load code when some is already running" + assert self.source is None, "you shouldn't load code when some is " \ + "already running" self.source = source self.code_greenlet = None @@ -126,7 +140,8 @@ def run_code(self, for_code=None): logger.debug('request received from code was %r', request) if not issubclass(request, RequestFromCodeGreenlet): - raise ValueError("Not a valid value from code greenlet: %r" % request) + raise ValueError("Not a valid value from code greenlet: %r" % + request) if request in [Wait, Refresh]: self.code_is_waiting = True if request == Refresh: @@ -142,12 +157,14 @@ def run_code(self, for_code=None): raise SystemExitFromCodeGreenlet() def sigint_handler(self, *args): - """SIGINT handler to use while code is running or request being fulfilled""" + """SIGINT handler to use while code is running or request being + fulfilled""" if greenlet.getcurrent() is self.code_greenlet: logger.debug('sigint while running user code!') raise KeyboardInterrupt() else: - logger.debug('sigint while fulfilling code request sigint handler running!') + logger.debug('sigint while fulfilling code request sigint handler ' + 'running!') self.sigint_happened_in_main_greenlet = True def _blocking_run_code(self): @@ -170,18 +187,22 @@ def request_from_main_greenlet(self, force_refresh=False): raise KeyboardInterrupt() return value + class FakeOutput(object): def __init__(self, coderunner, on_write): self.coderunner = coderunner self.on_write = on_write + def write(self, *args, **kwargs): self.on_write(*args, **kwargs) return self.coderunner.request_from_main_greenlet(force_refresh=True) + def writelines(self, l): for s in l: self.write(s) + def flush(self): pass + def isatty(self): return True - diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 07a426a63..3c44af3e1 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -76,7 +76,3 @@ def on_any_event(self, event): paths = [path + '.py' for path in self.dirs[dirpath]] if event.src_path in paths: self.on_change(files_modified=[event.src_path]) - -if __name__ == '__main__': - pass - diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index ce83ba5bf..8062bcddb 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -6,14 +6,17 @@ from bpython.curtsiesfrontend.events import RefreshRequestEvent from bpython.curtsiesfrontend.manual_readline import edit_keys + class StatusBar(BpythonInteraction): """StatusBar and Interaction for Repl Passing of control back and forth between calls that use interact api - (notify, confirm, file_prompt) like bpython.Repl.write2file and events - on the main thread happens via those calls and self.wait_for_request_or_notify. + (notify, confirm, file_prompt) like bpython.Repl.write2file and events on + the main thread happens via those calls and + self.wait_for_request_or_notify. - Calling one of these three is required for the main thread to regain control! + Calling one of these three is required for the main thread to regain + control! This is probably a terrible idea, and better would be rewriting this functionality in a evented or callback style, but trying to integrate @@ -62,7 +65,8 @@ def message(self, msg, schedule_refresh=True): self.schedule_refresh(time.time() + self.message_time) def _check_for_expired_message(self): - if self._message and time.time() > self.message_start_time + self.message_time: + if (self._message and + time.time() > self.message_start_time + self.message_time): self._message = '' def process_event(self, e): @@ -73,15 +77,17 @@ def process_event(self, e): self.request_greenlet.switch() elif isinstance(e, events.PasteEvent): for ee in e.events: - self.add_normal_character(ee if len(ee) == 1 else ee[-1]) #strip control seq + # strip control seq + self.add_normal_character(ee if len(ee) == 1 else ee[-1]) elif e in [''] or isinstance(e, events.SigIntEvent): self.request_greenlet.switch(False) self.escape() elif e in edit_keys: - self.cursor_offset_in_line, self._current_line = edit_keys[e](self.cursor_offset_in_line, self._current_line) - elif e == "": #TODO can this be removed? + self.cursor_offset_in_line, self._current_line = edit_keys[e]( + self.cursor_offset_in_line, self._current_line) + elif e == "": # TODO can this be removed? raise KeyboardInterrupt() - elif e == "": #TODO this isn't a very intuitive behavior + elif e == "": # TODO this isn't a very intuitive behavior raise SystemExit() elif self.in_prompt and e in ("\n", "\r", "", "Ctrl-m>"): line = self._current_line @@ -93,15 +99,17 @@ def process_event(self, e): else: self.request_greenlet.switch(False) self.escape() - else: # add normal character + else: # add normal character self.add_normal_character(e) def add_normal_character(self, e): - if e == '': e = ' ' - if len(e) > 1: return + if e == '': + e = ' ' + if len(e) > 1: + return self._current_line = (self._current_line[:self.cursor_offset_in_line] + - e + - self._current_line[self.cursor_offset_in_line:]) + e + + self._current_line[self.cursor_offset_in_line:]) self.cursor_offset_in_line += 1 def escape(self): @@ -137,7 +145,8 @@ def notify(self, msg, n=3, wait_for_keypress=False): self.request_refresh() self.main_greenlet.switch(msg) - # below Really ought to be called from greenlets other than main because they block + # below Really ought to be called from greenlets other than main because + # they block def confirm(self, q): """Expected to return True or False, given question prompt q""" self.request_greenlet = greenlet.getcurrent() diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 82c11380b..d302fe294 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -14,33 +14,33 @@ from bpython.repl import RuntimeTimer default_colors = { - Generic.Error:'R', - Keyword:'d', - Name:'c', - Name.Builtin:'g', - Comment:'b', - String:'m', - Error:'r', - Literal:'d', - Number:'M', - Number.Integer:'d', - Operator:'d', - Punctuation:'d', - Token:'d', - Whitespace:'d', - Token.Punctuation.Parenthesis:'R', - Name.Function:'d', - Name.Class:'d', - } + Generic.Error: 'R', + Keyword: 'd', + Name: 'c', + Name.Builtin: 'g', + Comment: 'b', + String: 'm', + Error: 'r', + Literal: 'd', + Number: 'M', + Number.Integer: 'd', + Operator: 'd', + Punctuation: 'd', + Token: 'd', + Whitespace: 'd', + Token.Punctuation.Parenthesis: 'R', + Name.Function: 'd', + Name.Class: 'd' +} + class BPythonFormatter(Formatter): - """This is subclassed from the custom formatter for bpython. - Its format() method receives the tokensource - and outfile params passed to it from the - Pygments highlight() method and slops - them into the appropriate format string - as defined above, then writes to the outfile - object the final formatted string. This does not write real strings. It writes format string (FmtStr) objects. + """This is subclassed from the custom formatter for bpython. Its format() + method receives the tokensource and outfile params passed to it from the + Pygments highlight() method and slops them into the appropriate format + string as defined above, then writes to the outfile object the final + formatted string. This does not write real strings. It writes format string + (FmtStr) objects. See the Pygments source for more info; it's pretty straightforward.""" @@ -60,6 +60,7 @@ def format(self, tokensource, outfile): o += "%s\x03%s\x04" % (self.f_strings[token], text) outfile.write(parse(o.rstrip())) + class Interp(code.InteractiveInterpreter): def __init__(self, locals=None): """Constructor. @@ -69,8 +70,8 @@ def __init__(self, locals=None): dictionary with key "__name__" set to "__console__" and key "__doc__" set to None. - We include an argument for the outfile to pass to the formatter for it to write to. - + We include an argument for the outfile to pass to the formatter for it + to write to. """ if locals is None: locals = {"__name__": "__console__", "__doc__": None} @@ -111,7 +112,7 @@ def showsyntaxerror(self, filename=None): l = traceback.format_exception_only(type, value) tbtext = ''.join(l) lexer = get_lexer_by_name("pytb") - self.format(tbtext,lexer) + self.format(tbtext, lexer) def showtraceback(self): """Display the exception that just occurred. @@ -149,7 +150,8 @@ def format(self, tbtext, lexer): traceback_code_formatter.format(cur_line, self.outfile) no_format_mode = False else: - traceback_informative_formatter.format(cur_line, self.outfile) + traceback_informative_formatter.format(cur_line, + self.outfile) cur_line = [] elif text == ' ' and cur_line == []: no_format_mode = True @@ -164,9 +166,9 @@ def runsource(self, source, filename="", symbol="single"): self, source, filename=filename, symbol=symbol) - def code_finished_will_parse(s, compiler): - """Returns a tuple of whether the buffer could be complete and whether it will parse + """Returns a tuple of whether the buffer could be complete and whether it + will parse True, True means code block is finished and no predicted parse error True, False means code block is finished because a parse error is predicted diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 985bc6248..6c9e20617 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -5,19 +5,22 @@ based on http://www.bigsmoke.us/readline/shortcuts""" from bpython.lazyre import LazyReCompile + import inspect +from six import iteritems INDENT = 4 -#TODO Allow user config of keybindings for these actions +# TODO Allow user config of keybindings for these actions + class AbstractEdits(object): default_kwargs = { - 'line': 'hello world', - 'cursor_offset': 5, - 'cut_buffer': 'there', - } + 'line': 'hello world', + 'cursor_offset': 5, + 'cut_buffer': 'there', + } def __contains__(self, key): try: @@ -34,22 +37,27 @@ def add(self, key, func, overwrite=False): else: raise ValueError('key %r already has a mapping' % (key,)) params = inspect.getargspec(func)[0] - args = dict((k, v) for k, v in self.default_kwargs.items() if k in params) + args = dict((k, v) for k, v in iteritems(self.default_kwargs) + if k in params) r = func(**args) if len(r) == 2: if hasattr(func, 'kills'): - raise ValueError('function %r returns two values, but has a kills attribute' % (func,)) + raise ValueError('function %r returns two values, but has a ' + 'kills attribute' % (func,)) self.simple_edits[key] = func elif len(r) == 3: if not hasattr(func, 'kills'): - raise ValueError('function %r returns three values, but has no kills attribute' % (func,)) + raise ValueError('function %r returns three values, but has ' + 'no kills attribute' % (func,)) self.cut_buffer_edits[key] = func else: - raise ValueError('return type of function %r not recognized' % (func,)) + raise ValueError('return type of function %r not recognized' % + (func,)) def add_config_attr(self, config_attr, func): if config_attr in self.awaiting_config: - raise ValueError('config attrribute %r already has a mapping' % (config_attr,)) + raise ValueError('config attrribute %r already has a mapping' % + (config_attr,)) self.awaiting_config[config_attr] = func def call(self, key, **kwargs): @@ -59,19 +67,25 @@ def call(self, key, **kwargs): return func(**args) def call_without_cut(self, key, **kwargs): - """Looks up the function and calls it, returning only line and cursor offset""" + """Looks up the function and calls it, returning only line and cursor + offset""" r = self.call_for_two(key, **kwargs) return r[:2] def __getitem__(self, key): - if key in self.simple_edits: return self.simple_edits[key] - if key in self.cut_buffer_edits: return self.cut_buffer_edits[key] + if key in self.simple_edits: + return self.simple_edits[key] + if key in self.cut_buffer_edits: + return self.cut_buffer_edits[key] raise KeyError("key %r not mapped" % (key,)) def __delitem__(self, key): - if key in self.simple_edits: del self.simple_edits[key] - elif key in self.cut_buffer_edits: del self.cut_buffer_edits[key] - else: raise KeyError("key %r not mapped" % (key,)) + if key in self.simple_edits: + del self.simple_edits[key] + elif key in self.cut_buffer_edits: + del self.cut_buffer_edits[key] + else: + raise KeyError("key %r not mapped" % (key,)) class UnconfiguredEdits(AbstractEdits): @@ -81,7 +95,8 @@ class UnconfiguredEdits(AbstractEdits): * func(**kwargs) -> cursor_offset, line * func(**kwargs) -> cursor_offset, line, cut_buffer where kwargs are in among the keys of Edits.default_kwargs - These functions will be run to determine their return type, so no side effects! + These functions will be run to determine their return type, so no side + effects! More concrete Edits instances can be created by applying a config with Edits.mapping_with_config() - this creates a new Edits instance @@ -101,8 +116,7 @@ def mapping_with_config(self, config, key_dispatch): self.awaiting_config, config, key_dispatch) def on(self, key=None, config=None): - if (key is None and config is None or - key is not None and config is not None): + if not ((key is None) ^ (config is None)): raise ValueError("Must use exactly one of key, config") if key is not None: def add_to_keybinds(func): @@ -117,7 +131,8 @@ def add_to_config(func): class ConfiguredEdits(AbstractEdits): - def __init__(self, simple_edits, cut_buffer_edits, awaiting_config, config, key_dispatch): + def __init__(self, simple_edits, cut_buffer_edits, awaiting_config, config, + key_dispatch): self.simple_edits = dict(simple_edits) self.cut_buffer_edits = dict(cut_buffer_edits) for attr, func in awaiting_config.items(): @@ -136,29 +151,35 @@ def add(self, key, func): # Because the edits.on decorator runs the functions, functions which depend # on other functions must be declared after their dependencies + def kills_behind(func): func.kills = 'behind' return func + def kills_ahead(func): func.kills = 'ahead' return func + @edit_keys.on(config='left_key') @edit_keys.on('') def left_arrow(cursor_offset, line): return max(0, cursor_offset - 1), line + @edit_keys.on(config='right_key') @edit_keys.on('') def right_arrow(cursor_offset, line): return min(len(line), cursor_offset + 1), line + @edit_keys.on(config='beginning_of_line_key') @edit_keys.on('') def beginning_of_line(cursor_offset, line): return 0, line + @edit_keys.on(config='end_of_line_key') @edit_keys.on('') def end_of_line(cursor_offset, line): @@ -176,35 +197,42 @@ def forward_word(cursor_offset, line): delta = match.end() - 1 if match else 0 return (cursor_offset + delta, line) + def last_word_pos(string): """returns the start index of the last word of given string""" match = forward_word_re.search(string[::-1]) index = match and len(string) - match.end() + 1 return index or 0 + @edit_keys.on('') @edit_keys.on('') @edit_keys.on('') def back_word(cursor_offset, line): return (last_word_pos(line[:cursor_offset]), line) + @edit_keys.on('') def delete(cursor_offset, line): return (cursor_offset, line[:cursor_offset] + line[cursor_offset+1:]) + @edit_keys.on('') @edit_keys.on(config='backspace_key') def backspace(cursor_offset, line): if cursor_offset == 0: return cursor_offset, line - if not line[:cursor_offset].strip(): #if just whitespace left of cursor - #front_white = len(line[:cursor_offset]) - len(line[:cursor_offset].lstrip()) + if not line[:cursor_offset].strip(): # if just whitespace left of cursor + # front_white = len(line[:cursor_offset]) - \ + # len(line[:cursor_offset].lstrip()) to_delete = ((cursor_offset - 1) % INDENT) + 1 - return cursor_offset - to_delete, line[:cursor_offset - to_delete] + line[cursor_offset:] + return (cursor_offset - to_delete, + line[:cursor_offset - to_delete] + line[cursor_offset:]) return (cursor_offset - 1, line[:cursor_offset - 1] + line[cursor_offset:]) + @edit_keys.on(config='clear_line_key') def delete_from_cursor_back(cursor_offset, line): return 0, line[cursor_offset:] @@ -213,13 +241,14 @@ def delete_from_cursor_back(cursor_offset, line): delete_rest_of_word_re = LazyReCompile(r'\w\b') -@edit_keys.on('') # option-d +@edit_keys.on('') # option-d @kills_ahead def delete_rest_of_word(cursor_offset, line): m = delete_rest_of_word_re.search(line[cursor_offset:]) if not m: return cursor_offset, line, '' - return (cursor_offset, line[:cursor_offset] + line[m.start()+cursor_offset+1:], + return (cursor_offset, + line[:cursor_offset] + line[m.start()+cursor_offset+1:], line[cursor_offset:m.start()+cursor_offset+1]) @@ -232,15 +261,22 @@ def delete_word_to_cursor(cursor_offset, line): start = 0 for match in delete_word_to_cursor_re.finditer(line[:cursor_offset]): start = match.start() + 1 - return start, line[:start] + line[cursor_offset:], line[start:cursor_offset] + return (start, line[:start] + line[cursor_offset:], + line[start:cursor_offset]) + @edit_keys.on('') -def yank_prev_prev_killed_text(cursor_offset, line, cut_buffer): #TODO not implemented - just prev - return cursor_offset+len(cut_buffer), line[:cursor_offset] + cut_buffer + line[cursor_offset:] +def yank_prev_prev_killed_text(cursor_offset, line, cut_buffer): + # TODO not implemented - just prev + return (cursor_offset+len(cut_buffer), + line[:cursor_offset] + cut_buffer + line[cursor_offset:]) + @edit_keys.on(config='yank_from_buffer_key') def yank_prev_killed_text(cursor_offset, line, cut_buffer): - return cursor_offset+len(cut_buffer), line[:cursor_offset] + cut_buffer + line[cursor_offset:] + return (cursor_offset+len(cut_buffer), + line[:cursor_offset] + cut_buffer + line[cursor_offset:]) + @edit_keys.on(config='transpose_chars_key') def transpose_character_before_cursor(cursor_offset, line): @@ -250,28 +286,33 @@ def transpose_character_before_cursor(cursor_offset, line): line[cursor_offset - 1] + line[cursor_offset+1:]) + @edit_keys.on('') def transpose_word_before_cursor(cursor_offset, line): - return cursor_offset, line #TODO Not implemented + return cursor_offset, line # TODO Not implemented # bonus functions (not part of readline) + @edit_keys.on('') def delete_line(cursor_offset, line): return 0, "" + @edit_keys.on('') def uppercase_next_word(cursor_offset, line): - return cursor_offset, line #TODO Not implemented + return cursor_offset, line # TODO Not implemented + @edit_keys.on(config='cut_to_buffer_key') @kills_ahead def delete_from_cursor_forward(cursor_offset, line): return cursor_offset, line[:cursor_offset], line[cursor_offset:] + @edit_keys.on('') def titlecase_next_word(cursor_offset, line): - return cursor_offset, line #TODO Not implemented + return cursor_offset, line # TODO Not implemented delete_word_from_cursor_back_re = LazyReCompile(r'\b\w') @@ -289,6 +330,7 @@ def delete_word_from_cursor_back(cursor_offset, line): if match.start() < cursor_offset: start = match.start() if start is not None: - return start, line[:start] + line[cursor_offset:], line[start:cursor_offset] + return (start, line[:start] + line[cursor_offset:], + line[start:cursor_offset]) else: return cursor_offset, line, '' diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index c8d2f0ac5..a09d17707 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -10,6 +10,7 @@ cnames = dict(zip('krgybmcwd', colors + ('default',))) + def func_for_letter(l, default='k'): """Returns FmtStr constructor for a bpython-style color code""" if l == 'd': @@ -18,11 +19,13 @@ def func_for_letter(l, default='k'): l = default.upper() return partial(fmtstr, fg=cnames[l.lower()], bold=l.isupper()) + def color_for_letter(l, default='k'): if l == 'd': l = default return cnames[l.lower()] + def parse(s): """Returns a FmtStr object from a bpython-formatted colored string""" rest = s @@ -36,6 +39,7 @@ def parse(s): if len(stuff) > 0 else FmtStr()) + def fs_from_match(d): atts = {} if d['fg']: @@ -43,14 +47,16 @@ def fs_from_match(d): # this isn't according to spec as I understand it if d['fg'].isupper(): d['bold'] = True - #TODO figure out why boldness isn't based on presence of \x02 + # TODO figure out why boldness isn't based on presence of \x02 color = cnames[d['fg'].lower()] if color != 'default': atts['fg'] = FG_COLORS[color] if d['bg']: if d['bg'] == 'I': - color = colors[(colors.index(color) + (len(colors) // 2)) % len(colors)] # hack for finding the "inverse" + # hack for finding the "inverse" + color = colors[(colors.index(color) + (len(colors) // 2)) % + len(colors)] else: color = cnames[d['bg'].lower()] if color != 'default': diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index 4ee834438..b67a238ba 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -1,8 +1,9 @@ -"""Tools for preparing code to be run in the REPL (removing blank lines, etc)""" +"""Tools for preparing code to be run in the REPL (removing blank lines, +etc)""" from bpython.lazyre import LazyReCompile -#TODO specifically catch IndentationErrors instead of any syntax errors +# TODO specifically catch IndentationErrors instead of any syntax errors indent_empty_lines_re = LazyReCompile(r'\s*') @@ -19,7 +20,8 @@ def indent_empty_lines(s, compiler): lines.pop() result_lines = [] - for p_line, line, n_line in zip([''] + lines[:-1], lines, lines[1:] + ['']): + for p_line, line, n_line in zip([''] + lines[:-1], lines, + lines[1:] + ['']): if len(line) == 0: p_indent = indent_empty_lines_re.match(p_line).group() n_indent = indent_empty_lines_re.match(n_line).group() @@ -28,4 +30,3 @@ def indent_empty_lines(s, compiler): result_lines.append(line) return '\n'.join(result_lines) + ('\n' if ends_with_newline else '') - diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index a05b89ec4..02d11582a 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -47,7 +47,8 @@ from bpython.curtsiesfrontend.parse import parse as bpythonparse from bpython.curtsiesfrontend.parse import func_for_letter, color_for_letter from bpython.curtsiesfrontend.preprocess import indent_empty_lines -from bpython.curtsiesfrontend.interpreter import Interp, code_finished_will_parse +from bpython.curtsiesfrontend.interpreter import Interp, \ + code_finished_will_parse #TODO other autocomplete modes (also fix in other bpython implementations) @@ -108,7 +109,8 @@ def process_event(self, e): if ee not in self.rl_char_sequences: self.add_input_character(ee) elif e in self.rl_char_sequences: - self.cursor_offset, self.current_line = self.rl_char_sequences[e](self.cursor_offset, self.current_line) + self.cursor_offset, self.current_line = self.rl_char_sequences[e]( + self.cursor_offset, self.current_line) elif isinstance(e, events.SigIntEvent): self.coderunner.sigint_happened_in_main_greenlet = True self.has_focus = False @@ -136,7 +138,7 @@ def process_event(self, e): self.current_line = '' self.cursor_offset = 0 self.repl.run_code_and_maybe_finish(for_code=line+'\n') - else: # add normal character + else: # add normal character self.add_input_character(e) if self.current_line.endswith(("\n", "\r")): @@ -188,7 +190,8 @@ def encoding(self): #TODO write a read() method class ReevaluateFakeStdin(object): - """Stdin mock used during reevaluation (undo) so raw_inputs don't have to be reentered""" + """Stdin mock used during reevaluation (undo) so raw_inputs don't have to + be reentered""" def __init__(self, fakestdin, repl): self.fakestdin = fakestdin self.repl = repl @@ -205,19 +208,20 @@ class Repl(BpythonRepl): """Python Repl Reacts to events like - -terminal dimensions and change events - -keystrokes + - terminal dimensions and change events + - keystrokes Behavior altered by - -number of scroll downs that were necessary to render array after each display - -initial cursor position + - number of scroll downs that were necessary to render array after each + display + - initial cursor position outputs: - -2D array to be rendered + - 2D array to be rendered - Repl is mostly view-independent state of Repl - but self.width and self.height - are important for figuring out how to wrap lines for example. - Usually self.width and self.height should be set by receiving a window resize event, - not manually set to anything - as long as the first event received is a window - resize event, this works fine. + Repl is mostly view-independent state of Repl - but self.width and + self.height are important for figuring out how to wrap lines for example. + Usually self.width and self.height should be set by receiving a window + resize event, not manually set to anything - as long as the first event + received is a window resize event, this works fine. """ ## initialization, cleanup @@ -240,12 +244,12 @@ def __init__(self, locals_ is a mapping of locals to pass into the interpreter config is a bpython config.Struct with config attributes request_refresh is a function that will be called when the Repl - wants to refresh the display, but wants control returned to it afterwards - Takes as a kwarg when= which is when to fire - get_term_hw is a function that returns the current width and height - of the terminal - get_cursor_vertical_diff is a function that returns how the cursor moved - due to a window size change + wants to refresh the display, but wants control returned to it + afterwards Takes as a kwarg when= which is when to fire + get_term_hw is a function that returns the current width and height of + the terminal + get_cursor_vertical_diff is a function that returns how the cursor + moved due to a window size change banner is a string to display briefly in the status bar interp is an interpreter to use """ @@ -256,18 +260,21 @@ def __init__(self, config = Struct() loadini(config, default_config_path()) - self.weak_rewind = bool(locals_ or interp) # If creating a new interpreter on undo - # would be unsafe because initial - # state was passed in + # If creating a new interpreter on undo would be unsafe because initial + # state was passed in + self.weak_rewind = bool(locals_ or interp) + if interp is None: interp = Interp(locals=locals_) interp.writetb = self.send_to_stderr if banner is None: if config.help_key: - banner = _('Welcome to bpython!') + ' ' + (_('Press <%s> for help.') % config.help_key) + banner = ' '.join((_('Welcome to bpython!'), + _('Press <%s> for help.') % config.help_key)) else: banner = None - config.autocomplete_mode = autocomplete.SIMPLE # only one implemented currently + # only one implemented currently + config.autocomplete_mode = autocomplete.SIMPLE if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1: config.cli_suggestion_width = 1 @@ -301,7 +308,8 @@ def smarter_request_reload(files_modified=()): self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch) logger.debug("starting parent init") super(Repl, self).__init__(interp, config) - #TODO bring together all interactive stuff - including current directory in path? + # TODO bring together all interactive stuff - including current + # directory in path? self.formatter = BPythonFormatter(config.color_scheme) self.interact = self.status_bar # overwriting what bpython.Repl put there # interact is called to interact with the status bar, @@ -366,11 +374,13 @@ def __enter__(self): self.orig_import = __builtins__['__import__'] if self.watcher: - old_module_locations = {} # for reading modules if they fail to load + # for reading modules if they fail to load + old_module_locations = {} @functools.wraps(self.orig_import) def new_import(name, globals={}, locals={}, fromlist=[], level=-1): try: - m = self.orig_import(name, globals=globals, locals=locals, fromlist=fromlist, level=level) + m = self.orig_import(name, globals=globals, locals=locals, + fromlist=fromlist, level=level) except: if name in old_module_locations: loc = old_module_locations[name] @@ -398,8 +408,10 @@ def sigwinch_handler(self, signum, frame): self.height, self.width = self.get_term_hw() cursor_dy = self.get_cursor_vertical_diff() self.scroll_offset -= cursor_dy - logger.info('sigwinch! Changed from %r to %r', (old_rows, old_columns), (self.height, self.width)) - logger.info('decreasing scroll offset by %d to %d', cursor_dy, self.scroll_offset) + logger.info('sigwinch! Changed from %r to %r', (old_rows, old_columns), + (self.height, self.width)) + logger.info('decreasing scroll offset by %d to %d', cursor_dy, + self.scroll_offset) def sigtstp_handler(self, signum, frame): self.scroll_offset = len(self.lines_for_display) @@ -431,7 +443,8 @@ def process_event(self, e): def process_control_event(self, e): if isinstance(e, bpythonevents.ScheduledRefreshRequestEvent): - pass # This is a scheduled refresh - it's really just a refresh (so nop) + # This is a scheduled refresh - it's really just a refresh (so nop) + pass elif isinstance(e, bpythonevents.RefreshRequestEvent): logger.info('received ASAP refresh request event') @@ -450,7 +463,8 @@ def process_control_event(self, e): if ctrl_char is not None: return self.process_event(ctrl_char) simple_events = just_simple_events(e.events) - source = indent_empty_lines(''.join(simple_events), self.interp.compile) + source = indent_empty_lines(''.join(simple_events), + self.interp.compile) with self.in_paste_mode(): for ee in source: @@ -489,12 +503,14 @@ def process_control_event(self, e): raise ValueError("Don't know how to handle event type: %r" % e) def process_key_event(self, e): - # To find the curtsies name for a keypress, try python -m curtsies.events + # To find the curtsies name for a keypress, try + # python -m curtsies.events if self.status_bar.has_focus: return self.status_bar.process_event(e) if self.stdin.has_focus: return self.stdin.process_event(e) - if (e in ("", '') and self.config.curtsies_right_arrow_completion - and self.cursor_offset == len(self.current_line)): + if (e in ("", '') and + self.config.curtsies_right_arrow_completion and + self.cursor_offset == len(self.current_line)): self.current_line += self.current_suggestion self.cursor_offset = len(self.current_line) @@ -554,7 +570,7 @@ def process_key_event(self, e): self.send_session_to_external_editor() elif e in key_dispatch[self.config.edit_config_key]: greenlet.greenlet(self.edit_config).switch() - #TODO add PAD keys hack as in bpython.cli + # TODO add PAD keys hack as in bpython.cli elif e in key_dispatch[self.config.edit_current_block_key]: self.send_current_block_to_external_editor() elif e in [""]: #ESC @@ -601,7 +617,8 @@ def incremental_search(self, reverse=False, include_current=False): def readline_kill(self, e): func = self.edit_keys[e] - self.cursor_offset, self.current_line, cut = func(self.cursor_offset, self.current_line) + self.cursor_offset, self.current_line, cut = func(self.cursor_offset, + self.current_line) if self.last_events[-2] == e: # consecutive kill commands are cumulative if func.kills == 'ahead': self.cut_buffer += cut elif func.kills == 'behind': self.cut_buffer = cut + self.cut_buffer @@ -1421,7 +1438,8 @@ def version_help_text(self): ) def key_help_text(self): - NOT_IMPLEMENTED = ['suspend', 'cut to buffer', 'search', 'last output', 'yank from buffer', 'cut to buffer'] + NOT_IMPLEMENTED = ('suspend', 'cut to buffer', 'search', 'last output', + 'yank from buffer', 'cut to buffer') pairs = [] pairs.append(['complete history suggestion', 'right arrow at end of line']) pairs.append(['previous match with current line', 'up arrow']) @@ -1445,16 +1463,19 @@ def tabs_to_spaces(line): return line.replace('\t', ' ') def compress_paste_event(paste_event): - """If all events in a paste event are identical and not simple characters, returns one of them + """If all events in a paste event are identical and not simple characters, + returns one of them - Useful for when the UI is running so slowly that repeated keypresses end up in a paste event. - If we value not getting delayed and assume the user is holding down a key to produce such frequent - key events, it makes sense to drop some of the events. + Useful for when the UI is running so slowly that repeated keypresses end up + in a paste event. If we value not getting delayed and assume the user is + holding down a key to produce such frequent key events, it makes sense to + drop some of the events. """ if not all(paste_event.events[0] == e for e in paste_event.events): return None event = paste_event.events[0] - if len(event) > 1:# basically "is there a special curtsies names for this key?" + # basically "is there a special curtsies names for this key?" + if len(event) > 1: return event else: return None @@ -1462,7 +1483,8 @@ def compress_paste_event(paste_event): def just_simple_events(event_list): simple_events = [] for e in event_list: - if e in (u"", u"", u"", u"\n", u"\r"): # '\n' necessary for pastes + # '\n' necessary for pastes + if e in (u"", u"", u"", u"\n", u"\r"): simple_events.append(u'\n') elif isinstance(e, events.Event): pass # ignore events @@ -1473,7 +1495,7 @@ def just_simple_events(event_list): return simple_events -#TODO this needs some work to function again and be useful for embedding +# TODO this needs some work to function again and be useful for embedding def simple_repl(): refreshes = [] def request_refresh(): @@ -1487,5 +1509,6 @@ def request_refresh(): r.dumb_print_output() r.dumb_input(refreshes) + if __name__ == '__main__': simple_repl() diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index bb94c9bab..1b0db9343 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -19,6 +19,7 @@ # * return an array of the width they were asked for # * return an array not taller than the height they were asked for + def display_linize(msg, columns, blank_line=False): """Returns lines obtained by splitting msg over multiple lines. @@ -30,6 +31,7 @@ def display_linize(msg, columns, blank_line=False): if msg else ([''] if blank_line else [])) return display_lines + def paint_history(rows, columns, display_lines): lines = [] for r, line in zip(range(rows), display_lines[-rows:]): @@ -39,10 +41,12 @@ def paint_history(rows, columns, display_lines): assert r.shape[1] <= columns, repr(r.shape)+' '+repr(columns) return r + def paint_current_line(rows, columns, current_display_line): lines = display_linize(current_display_line, columns, True) return fsarray(lines, width=columns) + def matches_lines(rows, columns, matches, current, config, format): highlight_color = func_for_letter(config.color_scheme['operator'].lower()) @@ -57,7 +61,8 @@ def matches_lines(rows, columns, matches, current, config, format): matches_lines = [fmtstr(' ').join(color(m.ljust(max_match_width)) if m != current - else highlight_color(m.ljust(max_match_width)) + else highlight_color( + m.ljust(max_match_width)) for m in matches[i:i+words_wide]) for i in range(0, len(matches), words_wide)] @@ -65,14 +70,15 @@ def matches_lines(rows, columns, matches, current, config, format): logger.debug('matches_lines: %r' % matches_lines) return matches_lines + def formatted_argspec(argspec, columns, config): # Pretty directly taken from bpython.cli is_bound_method = argspec[2] func = argspec[0] args = argspec[1][0] kwargs = argspec[1][3] - _args = argspec[1][1] #*args - _kwargs = argspec[1][2] #**kwargs + _args = argspec[1][1] # *args + _kwargs = argspec[1][2] # **kwargs is_bound_method = argspec[2] in_arg = argspec[3] if py3: @@ -84,11 +90,12 @@ def formatted_argspec(argspec, columns, config): punctuation_color = func_for_letter(config.color_scheme['punctuation']) token_color = func_for_letter(config.color_scheme['token']) bolds = {token_color: lambda x: bold(token_color(x)), - arg_color: lambda x: bold(arg_color(x))} + arg_color: lambda x: bold(arg_color(x))} s = func_color(func) + arg_color(': (') - if is_bound_method and isinstance(in_arg, int): #TODO what values could this have? + if is_bound_method and isinstance(in_arg, int): + # TODO what values could this have? in_arg += 1 for i, arg in enumerate(args): @@ -141,19 +148,25 @@ def formatted_argspec(argspec, columns, config): return linesplit(s, columns) + def formatted_docstring(docstring, columns, config): color = func_for_letter(config.color_scheme['comment']) - return sum(([color(x) for x in (display_linize(line, columns) if line else fmtstr(''))] + return sum(([color(x) for x in (display_linize(line, columns) if line else + fmtstr(''))] for line in docstring.split('\n')), []) -def paint_infobox(rows, columns, matches, argspec, match, docstring, config, format): + +def paint_infobox(rows, columns, matches, argspec, match, docstring, config, + format): """Returns painted completions, argspec, match, docstring etc.""" if not (rows and columns): return fsarray(0, 0) width = columns - 4 lines = ((formatted_argspec(argspec, width, config) if argspec else []) + - (matches_lines(rows, width, matches, match, config, format) if matches else []) + - (formatted_docstring(docstring, width, config) if docstring else [])) + (matches_lines(rows, width, matches, match, config, format) + if matches else []) + + (formatted_docstring(docstring, width, config) + if docstring else [])) def add_border(line): """Add colored borders left and right to a line.""" @@ -173,19 +186,27 @@ def add_border(line): output_lines = list(itertools.chain((top_line, ), map(add_border, lines), (bottom_line, ))) - r = fsarray(output_lines[:min(rows-1, len(output_lines)-1)] + output_lines[-1:]) + r = fsarray(output_lines[:min(rows - 1, + len(output_lines) - 1)] + output_lines[-1:]) return r + def paint_last_events(rows, columns, names, config): if not names: return fsarray([]) - width = min(max(len(name) for name in names), columns-2) + width = min(max(len(name) for name in names), columns - 2) output_lines = [] - output_lines.append(config.left_top_corner+config.top_border*(width)+config.right_top_corner) - for name in reversed(names[max(0, len(names)-(rows-2)):]): - output_lines.append(config.left_border+name[:width].center(width)+config.right_border) - output_lines.append(config.left_bottom_corner+config.bottom_border*width+config.right_bottom_corner) + output_lines.append(config.left_top_corner+config.top_border * width + + config.right_top_corner) + for name in reversed(names[max(0, len(names) - (rows - 2)):]): + output_lines.append(config.left_border + name[:width].center(width) + + config.right_border) + output_lines.append(config.left_bottom_corner + + config.bottom_border * width + + config.right_bottom_corner) return fsarray(output_lines) + def paint_statusbar(rows, columns, msg, config): - return fsarray([func_for_letter(config.color_scheme['main'])(msg.ljust(columns))[:columns]]) + func = func_for_letter(config.color_scheme['main']) + return fsarray([func(msg.ljust(columns))[:columns]]) diff --git a/bpython/curtsiesfrontend/sitefix.py b/bpython/curtsiesfrontend/sitefix.py index f584fc36d..37a6ae954 100644 --- a/bpython/curtsiesfrontend/sitefix.py +++ b/bpython/curtsiesfrontend/sitefix.py @@ -1,8 +1,8 @@ -"""""" import sys from bpython._py3compat import py3 + def resetquit(builtins): """Redefine builtins 'quit' and 'exit' not so close stdin @@ -12,6 +12,7 @@ def __call__(self, code=None): __call__.__name__ = 'FakeQuitCall' builtins.quit.__class__.__call__ = __call__ + def monkeypatch_quit(): if 'site' in sys.modules: resetquit(sys.modules['builtins' if py3 else '__builtin__']) From 871d2931cf5931aa6915373d95948c853129ad38 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 Feb 2015 23:42:35 +0100 Subject: [PATCH 0390/1650] Update documentation Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/releases.rst | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst index 8443628d3..52e64696f 100644 --- a/doc/sphinx/source/releases.rst +++ b/doc/sphinx/source/releases.rst @@ -5,26 +5,28 @@ Releases Release schedule ---------------- -bpython does not have a set release cycle. The developers will decide together when the time is ripe to release a version. -For information what happens after the decision is made to make a release you should read the 'Release Path' section. +bpython does not have a set release cycle. The developers will decide together +when the time is ripe to release a version. For information what happens after +the decision is made to make a release you should read the 'Release Path' +section. Release Path ------------ -After it is decided to release a new version of bpython the following checklist is followed: +After it is decided to release a new version of bpython the following checklist +is followed: * The repository is frozen, nobody pushes until the version is built. -* Bob (:ref:`authors`) makes a tarball of the new version and sends it to Simon (:ref:`authors`) who will host it on the bpython website. +* Bob (:ref:`authors`) makes a tarball of the new version and sends it to Simon + (:ref:`authors`) who will host it on the bpython website. * The package is then downloaded by all of the people who like to test it. * Everybody checks if there are no great problems: - - * Version numbers correct? - * CHANGELOG is correct? - * AUTHORS? - -* After everybody says 'yes' the website and pypi are updated to point to this new version. - - * Simon (:ref:`authors`) also checks if all numbers on the website have been updated. - + - Version numbers correct? + - CHANGELOG is correct? + - AUTHORS? +* After everybody says 'yes' the website and PyPI are updated to point to this + new version. + - Simon (:ref:`authors`) also checks if all numbers on the website have been + updated. * 24 hours later package maintainers could update their stuff. Checklist @@ -34,7 +36,7 @@ A checklist to perform some manual tests before a release: Check that all of the following work before a release: -* Runs under Python 2.6, 2.7, 3.3, and 3.4 (after 2to3). +* Runs under Python 2.6, 2.7, 3.3, and 3.4. * Save * Rewind * Pastebin From a3f43bad2616dcc8a88f3986c8bb11b3dd2c0415 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 01:26:52 +0100 Subject: [PATCH 0391/1650] Encode arguments to shlex in Python < 2.7.3 Signed-off-by: Sebastian Ramacher --- bpython/_py3compat.py | 7 +++++++ bpython/repl.py | 22 +++++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index dd3c489fd..91069e97e 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -42,3 +42,10 @@ from pygments.lexers import Python3Lexer as PythonLexer else: from pygments.lexers import PythonLexer + +if py3 or sys.version_info[:3] >= (2, 7, 3): + def prepare_for_exec(arg, encoding=None): + return arg +else: + def prepare_for_exec(arg, encoding=None): + return arg.encode(encoding) diff --git a/bpython/repl.py b/bpython/repl.py index 8160419b7..95619b5b3 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -45,7 +45,7 @@ from pygments.token import Token from bpython import inspection -from bpython._py3compat import PythonLexer, py3 +from bpython._py3compat import PythonLexer, py3, prepare_for_exec from bpython.formatter import Parenthesis from bpython.translations import _, ngettext from bpython.clipboard import get_clipboard, CopyFailed @@ -1029,22 +1029,30 @@ def clear_current_line(self): def send_to_external_editor(self, text, filename=None): """Returns modified text from an editor, or the oriignal text if editor exited with non-zero""" - editor_args = shlex.split(self.config.editor) + + encoding = getpreferredencoding() + editor_args = shlex.split(prepare_for_exec(self.config.editor, + encoding)) with tempfile.NamedTemporaryFile(suffix='.py') as temp: - temp.write(text.encode(getpreferredencoding())) + temp.write(text.encode(encoding)) temp.flush() - if subprocess.call(editor_args + [temp.name]) == 0: + + args = editor_args + [prepare_for_exec(temp.name, encoding)] + if subprocess.call(args) == 0: with open(temp.name) as f: if py3: return f.read() else: - return f.read().decode(getpreferredencoding()) + return f.read().decode(encoding) else: return text def open_in_external_editor(self, filename): - editor_args = shlex.split(self.config.editor) - if subprocess.call(editor_args + [filename]) == 0: + encoding = getpreferredencoding() + editor_args = shlex.split(prepare_for_exec(self.config.editor, + encoding)) + args = editor_args + [prepare_for_exec(filename, encoding)] + if subprocess.call(args) == 0: return True return False From 50d2b21c63fe823cfb19356856c5852d510a1960 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 6 Feb 2015 20:25:41 -0500 Subject: [PATCH 0392/1650] prevent tests failing when PYTHONSTARTUP set also rename poorly named parent test class --- bpython/test/test_curtsies_painting.py | 25 ++++++++++++++++++++----- bpython/test/test_curtsies_repl.py | 1 + 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 2f24a3d09..420a5230c 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -6,8 +6,9 @@ from curtsies.formatstringarray import FormatStringTest, fsarray from curtsies.fmtfuncs import cyan, bold, green, yellow, on_magenta, red -from bpython.curtsiesfrontend.events import RefreshRequestEvent +from bpython.curtsiesfrontend.events import RefreshRequestEvent +from bpython.test import mock from bpython import config, inspection from bpython.curtsiesfrontend.repl import Repl from bpython.curtsiesfrontend import replpainter @@ -23,7 +24,21 @@ def setup_config(): return config_struct -class TestCurtsiesPainting(FormatStringTest, TestCase): +class ClearEnviron(TestCase): + + @classmethod + def setUpClass(cls): + cls.mock_environ = mock.patch.dict('os.environ', {}, clear=True) + cls.mock_environ.start() + TestCase.setUpClass() + + @classmethod + def tearDownClass(cls): + cls.mock_environ.stop() + TestCase.tearDownClass() + + +class CurtsiesPaintingTest(FormatStringTest, ClearEnviron): def setUp(self): self.repl = Repl(config=setup_config()) # clear history @@ -42,13 +57,13 @@ def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None): self.assertEqual(cursor_pos, cursor_row_col) -class TestCurtsiesPaintingTest(TestCurtsiesPainting): +class TestCurtsiesPaintingTest(CurtsiesPaintingTest): def test_history_is_cleared(self): self.assertEqual(self.repl.rl_history.entries, ['']) -class TestCurtsiesPaintingSimple(TestCurtsiesPainting): +class TestCurtsiesPaintingSimple(CurtsiesPaintingTest): def test_startup(self): screen = fsarray([cyan('>>> '), cyan('Welcome to')]) @@ -137,7 +152,7 @@ def output_to_repl(repl): sys.stdout, sys.stderr = old_out, old_err -class TestCurtsiesRewindRedraw(TestCurtsiesPainting): +class TestCurtsiesRewindRedraw(CurtsiesPaintingTest): def refresh(self): self.refresh_requests.append(RefreshRequestEvent()) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index b201409ba..685c64677 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -221,6 +221,7 @@ def create_repl(**kwargs): config = setup_config({'editor': 'true'}) repl = curtsiesrepl.Repl(config=config, **kwargs) os.environ['PAGER'] = 'true' + os.environ.pop('PYTHONSTARTUP', None) repl.width = 50 repl.height = 20 return repl From 3fb84419ba4270c8d74dcf966ba5099a28d903c0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 6 Feb 2015 23:14:17 -0500 Subject: [PATCH 0393/1650] test running startup file --- bpython/test/__init__.py | 6 ++++++ bpython/test/test_curtsies_repl.py | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index 94f33e6af..7aa5d1a9d 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -12,6 +12,7 @@ from bpython.translations import init from bpython._py3compat import py3 +from six.moves import builtins class FixLanguageTestCase(unittest.TestCase): @@ -27,3 +28,8 @@ class MagicIterMock(mock.MagicMock): __next__ = mock.Mock(return_value=None) else: next = mock.Mock(return_value=None) + + +def builtin_target(obj): + """Returns mock target string of a builtin""" + return '%s.%s' % (builtins.__name__, obj.__name__) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 685c64677..5eb9798e4 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -15,11 +15,13 @@ from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter +from bpython.curtsiesfrontend import events as bpythonevents from bpython import autocomplete from bpython import config from bpython import args from bpython._py3compat import py3 -from bpython.test import FixLanguageTestCase as TestCase, MagicIterMock, mock +from bpython.test import (FixLanguageTestCase as TestCase, MagicIterMock, mock, + builtin_target) def setup_config(conf): @@ -307,5 +309,20 @@ def test_show_source_formatted(self): self.repl.show_source() +class TestCurtsiesStartup(TestCase): + + def setUp(self): + self.repl = create_repl() + os.environ['PYTHONSTARTUP'] = 'file' + + def tearDown(self): + del os.environ['PYTHONSTARTUP'] + + @mock.patch(builtin_target(open), mock.mock_open(read_data='a = 1\n')) + def test_startup_event(self): + self.repl.process_event(bpythonevents.RunStartupFileEvent()) + self.assertIn('a', self.repl.interp.locals) + + if __name__ == '__main__': unittest.main() From 8bdb42e2d02391887fc2d1d9fb31fd89a83baef4 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 6 Feb 2015 23:17:03 -0500 Subject: [PATCH 0394/1650] Add NOP encode parameter to curtsies interpreter --- bpython/curtsiesfrontend/interpreter.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index d302fe294..5354a78a6 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -11,7 +11,7 @@ from pygments.lexers import get_lexer_by_name from bpython.curtsiesfrontend.parse import parse -from bpython.repl import RuntimeTimer +from bpython.repl import Interpreter as ReplInterpreter default_colors = { Generic.Error: 'R', @@ -61,7 +61,7 @@ def format(self, tokensource, outfile): outfile.write(parse(o.rstrip())) -class Interp(code.InteractiveInterpreter): +class Interp(ReplInterpreter): def __init__(self, locals=None): """Constructor. @@ -75,13 +75,13 @@ def __init__(self, locals=None): """ if locals is None: locals = {"__name__": "__console__", "__doc__": None} + ReplInterpreter.__init__(self, locals) self.locals = locals self.compile = CommandCompiler() # typically changed after being instantiated self.write = lambda stuff: sys.stderr.write(stuff) self.outfile = self - self.timer = RuntimeTimer() def showsyntaxerror(self, filename=None): """Display the syntax error that just occurred. @@ -160,11 +160,12 @@ def format(self, tbtext, lexer): cur_line.append((token, text)) assert cur_line == [], cur_line - def runsource(self, source, filename="", symbol="single"): + def runsource(self, source, filename="", symbol="single", + encode=None): + # TODO: encode does nothing with self.timer: return code.InteractiveInterpreter.runsource( - self, source, filename=filename, symbol=symbol) - + self, source, filename, symbol) def code_finished_will_parse(s, compiler): """Returns a tuple of whether the buffer could be complete and whether it From b6a2a5f6fe0d64ba31db7faadab91855cbc31649 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 7 Feb 2015 00:58:28 -0500 Subject: [PATCH 0395/1650] test behavior requiring encoding in runsource --- bpython/test/test_interpreter.py | 44 +++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 83056c563..8a592b707 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -5,9 +5,12 @@ except ImportError: import unittest -from bpython.curtsiesfrontend import interpreter from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain +from bpython.curtsiesfrontend import interpreter +from bpython._py3compat import py3 +from bpython.test import mock + class TestInterpreter(unittest.TestCase): def test_syntaxerror(self): @@ -51,3 +54,42 @@ def g(): self.assertEquals(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) + + @unittest.skipIf(py3, "runsource() accepts only unicode in Python 3") + def test_runsource_bytes(self): + i = interpreter.Interp() + i.encoding = 'latin-1' + + i.runsource(b"a = b'\xfe'") + self.assertIsInstance(i.locals['a'], str) + self.assertEqual(i.locals['a'], b"\xfe") + + i.runsource(b"b = u'\xfe'") + self.assertIsInstance(i.locals['b'], unicode) + self.assertEqual(i.locals['b'], u"\xfe") + + @unittest.skipUnless(py3, "Only a syntax error in Python 3") + @mock.patch.object(interpreter.Interp, 'showsyntaxerror') + def test_runsource_bytes_over_128_syntax_error(self): + i = interpreter.Interp() + i.encoding = 'latin-1' + + i.runsource(u"a = b'\xfe'") + i.showsyntaxerror.assert_called_with() + + @unittest.skipIf(py3, "only ASCII allowed in bytestrings in Python 3") + def test_runsource_bytes_over_128_syntax_error(self): + i = interpreter.Interp() + i.encoding = 'latin-1' + + i.runsource(u"a = b'\xfe'") + self.assertIsInstance(i.locals['a'], type(b'')) + self.assertEqual(i.locals['a'], b"\xfe") + + def test_runsource_unicode(self): + i = interpreter.Interp() + i.encoding = 'latin-1' + + i.runsource(u"a = u'\xfe'") + self.assertIsInstance(i.locals['a'], type(u'')) + self.assertEqual(i.locals['a'], u"\xfe") From 3cbe24dcad647cade2a58fba772b1de45f43e140 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 7 Feb 2015 01:50:46 -0500 Subject: [PATCH 0396/1650] delete repeated code in interpreters --- bpython/curtsiesfrontend/interpreter.py | 74 ++++++------------------- bpython/curtsiesfrontend/repl.py | 4 +- bpython/test/test_interpreter.py | 3 +- 3 files changed, 21 insertions(+), 60 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 5354a78a6..1945e1bac 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -12,6 +12,8 @@ from bpython.curtsiesfrontend.parse import parse from bpython.repl import Interpreter as ReplInterpreter +from bpython.config import getpreferredencoding +from bpython._py3compat import py3 default_colors = { Generic.Error: 'R', @@ -75,7 +77,8 @@ def __init__(self, locals=None): """ if locals is None: locals = {"__name__": "__console__", "__doc__": None} - ReplInterpreter.__init__(self, locals) + ReplInterpreter.__init__(self, locals, getpreferredencoding()) + self.locals = locals self.compile = CommandCompiler() @@ -83,58 +86,21 @@ def __init__(self, locals=None): self.write = lambda stuff: sys.stderr.write(stuff) self.outfile = self - def showsyntaxerror(self, filename=None): - """Display the syntax error that just occurred. - - This doesn't display a stack trace because there isn't one. - - If a filename is given, it is stuffed in the exception instead - of what was there before (because Python's parser always uses - "" when reading from a string). - - The output is written by self.write(), below. - - """ - type, value, sys.last_traceback = sys.exc_info() - sys.last_type = type - sys.last_value = value - if filename and type is SyntaxError: - # Work hard to stuff the correct filename in the exception - try: - msg, (dummy_filename, lineno, offset, line) = value - except: - # Not the format we expect; leave it alone - pass - else: - # Stuff in the right filename - value = SyntaxError(msg, (filename, lineno, offset, line)) - sys.last_value = value - l = traceback.format_exception_only(type, value) - tbtext = ''.join(l) + def writetb(self, lines): + tbtext = ''.join(lines) lexer = get_lexer_by_name("pytb") self.format(tbtext, lexer) - - def showtraceback(self): - """Display the exception that just occurred. - - We remove the first stack item because it is our own code. - - - """ - type, value, tb = sys.exc_info() - sys.last_type = type - sys.last_value = value - sys.last_traceback = tb - tblist = traceback.extract_tb(tb) - del tblist[:1] - l = traceback.format_list(tblist) - if l: - l.insert(0, "Traceback (most recent call last):\n") - l[len(l):] = traceback.format_exception_only(type, value) - tbtext = ''.join(l) - lexer = get_lexer_by_name("pytb", stripall=True) - - self.format(tbtext, lexer) + # TODO for tracebacks get_lexer_by_name("pytb", stripall=True) + + if not py3: + def runsource(self, source, filename="", symbol="single", + encode=True): + # TODO: write a test for and implement encoding + with self.timer: + if encode: + source = '#\n%s' % (source,) # dummy line so linenos match + return code.InteractiveInterpreter.runsource(self, source, + filename, symbol) def format(self, tbtext, lexer): traceback_informative_formatter = BPythonFormatter(default_colors) @@ -160,12 +126,6 @@ def format(self, tbtext, lexer): cur_line.append((token, text)) assert cur_line == [], cur_line - def runsource(self, source, filename="", symbol="single", - encode=None): - # TODO: encode does nothing - with self.timer: - return code.InteractiveInterpreter.runsource( - self, source, filename, symbol) def code_finished_will_parse(s, compiler): """Returns a tuple of whether the buffer could be complete and whether it diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 02d11582a..0f1f71eeb 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -266,7 +266,7 @@ def __init__(self, if interp is None: interp = Interp(locals=locals_) - interp.writetb = self.send_to_stderr + interp.write = self.send_to_stderr if banner is None: if config.help_key: banner = ' '.join((_('Welcome to bpython!'), @@ -1347,7 +1347,7 @@ def reevaluate(self, insert_into_history=False): if not self.weak_rewind: self.interp = self.interp.__class__() - self.interp.writetb = self.send_to_stderr + self.interp.write = self.send_to_stderr self.coderunner.interp = self.interp self.buffer = [] diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 8a592b707..97c7d596f 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -77,7 +77,8 @@ def test_runsource_bytes_over_128_syntax_error(self): i.runsource(u"a = b'\xfe'") i.showsyntaxerror.assert_called_with() - @unittest.skipIf(py3, "only ASCII allowed in bytestrings in Python 3") + @unittest.skip("Is the goal of encoding to get this to work?") + @unittest.skipIf(py3, "bytes 128-255 only permitted in Py 2") def test_runsource_bytes_over_128_syntax_error(self): i = interpreter.Interp() i.encoding = 'latin-1' From 75b4a0f1c1d0ef66ebb9b7176dc4627617089738 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 7 Feb 2015 10:33:11 -0500 Subject: [PATCH 0397/1650] fix #473 --- bpython/curtsiesfrontend/preprocess.py | 14 ++++++++++++++ bpython/curtsiesfrontend/repl.py | 8 ++++---- bpython/lazyre.py | 4 ++++ bpython/test/fodder/original.py | 2 ++ bpython/test/fodder/processed.py | 3 +++ bpython/test/test_preprocess.py | 17 ++++++++++------- 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index b67a238ba..0ecea6621 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -7,6 +7,7 @@ indent_empty_lines_re = LazyReCompile(r'\s*') +tabs_to_spaces_re = LazyReCompile(r'^\t+') def indent_empty_lines(s, compiler): @@ -30,3 +31,16 @@ def indent_empty_lines(s, compiler): result_lines.append(line) return '\n'.join(result_lines) + ('\n' if ends_with_newline else '') + + +def leading_tabs_to_spaces(s): + lines = s.split('\n') + result_lines = [] + tab_to_space = lambda m: len(m.group()) * 4 * ' ' + for line in lines: + result_lines.append(tabs_to_spaces_re.sub(tab_to_space, line)) + return '\n'.join(result_lines) + + +def preprocess(s, compiler): + return indent_empty_lines(leading_tabs_to_spaces(s), compiler) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 0f1f71eeb..42e4834f4 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -46,7 +46,7 @@ from bpython.curtsiesfrontend import events as bpythonevents from bpython.curtsiesfrontend.parse import parse as bpythonparse from bpython.curtsiesfrontend.parse import func_for_letter, color_for_letter -from bpython.curtsiesfrontend.preprocess import indent_empty_lines +from bpython.curtsiesfrontend.preprocess import preprocess from bpython.curtsiesfrontend.interpreter import Interp, \ code_finished_will_parse @@ -463,8 +463,8 @@ def process_control_event(self, e): if ctrl_char is not None: return self.process_event(ctrl_char) simple_events = just_simple_events(e.events) - source = indent_empty_lines(''.join(simple_events), - self.interp.compile) + source = preprocess(''.join(simple_events), + self.interp.compile) with self.in_paste_mode(): for ee in source: @@ -740,7 +740,7 @@ def send_session_to_external_editor(self, filename=None): text = self.send_to_external_editor(for_editor) lines = text.split('\n') from_editor = [line for line in lines if line[:4] != '### '] - source = indent_empty_lines('\n'.join(from_editor), self.interp.compile) + source = preprocess('\n'.join(from_editor), self.interp.compile) self.history = source.split('\n') self.reevaluate(insert_into_history=True) self.current_line = lines[-1][4:] diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 2f85f367c..62792e312 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -48,3 +48,7 @@ def search(self, *args, **kwargs): @compile_regex def match(self, *args, **kwargs): return self.compiled.match(*args, **kwargs) + + @compile_regex + def sub(self, *args, **kwargs): + return self.compiled.sub(*args, **kwargs) diff --git a/bpython/test/fodder/original.py b/bpython/test/fodder/original.py index f3c6f956c..1afd64129 100644 --- a/bpython/test/fodder/original.py +++ b/bpython/test/fodder/original.py @@ -45,3 +45,5 @@ def foo(): #EndTest +def tabs(): + return 1 diff --git a/bpython/test/fodder/processed.py b/bpython/test/fodder/processed.py index c4474856d..6b6331662 100644 --- a/bpython/test/fodder/processed.py +++ b/bpython/test/fodder/processed.py @@ -43,3 +43,6 @@ def foo(): return 1 #EndTest + +def tabs(): + return 1 diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index a81513b56..526f91c72 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -11,11 +11,11 @@ skip = unittest.skip from bpython.curtsiesfrontend.interpreter import code_finished_will_parse -from bpython.curtsiesfrontend.preprocess import indent_empty_lines +from bpython.curtsiesfrontend.preprocess import preprocess from bpython.test.fodder import original as original, processed -indent_empty = partial(indent_empty_lines, compiler=compiler) +preproc = partial(preprocess, compiler=compiler) def get_fodder_source(test_name): @@ -39,9 +39,9 @@ def assertCompiles(self, source): return finished and parsable def test_indent_empty_lines_nops(self): - self.assertEqual(indent_empty('hello'), 'hello') - self.assertEqual(indent_empty('hello\ngoodbye'), 'hello\ngoodbye') - self.assertEqual(indent_empty('a\n b\nc\n'), 'a\n b\nc\n') + self.assertEqual(preproc('hello'), 'hello') + self.assertEqual(preproc('hello\ngoodbye'), 'hello\ngoodbye') + self.assertEqual(preproc('a\n b\nc\n'), 'a\n b\nc\n') def assertShowWhitespaceEqual(self, a, b): self.assertEqual( @@ -57,12 +57,12 @@ def assertDefinitionIndented(self, obj): obj2 = getattr(processed, name) orig = inspect.getsource(obj) xformed = inspect.getsource(obj2) - self.assertShowWhitespaceEqual(indent_empty(orig), xformed) + self.assertShowWhitespaceEqual(preproc(orig), xformed) self.assertCompiles(xformed) def assertLinesIndented(self, test_name): orig, xformed = get_fodder_source(test_name) - self.assertShowWhitespaceEqual(indent_empty(orig), xformed) + self.assertShowWhitespaceEqual(preproc(orig), xformed) self.assertCompiles(xformed) def assertIndented(self, obj_or_name): @@ -92,3 +92,6 @@ def test_blank_line_in_try_catch_else(self): def test_blank_trailing_line(self): self.assertIndented('blank_trailing_line') + + def test_tabs(self): + self.assertIndented(original.tabs) From f7aa4b299f017099258b58d71b598c8d48bcf676 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 7 Feb 2015 10:35:25 -0500 Subject: [PATCH 0398/1650] mark another test as slow --- bpython/test/test_args.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index daf8d58ee..a8e568ae2 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -8,7 +8,14 @@ except ImportError: import unittest +try: + from nose.plugins.attrib import attr +except ImportError: + def attr(func, *args, **kwargs): + return func + +@attr(speed='slow') class TestExecArgs(unittest.TestCase): def test_exec_dunder_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: From 02631dd6aa6dde251422187fd2bec82adbef6f66 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 7 Feb 2015 10:49:32 -0500 Subject: [PATCH 0399/1650] pep8 of curtsies repl --- bpython/curtsiesfrontend/repl.py | 66 +++++++++++++++++++------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 42e4834f4..3c6ade723 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -29,8 +29,8 @@ import bpython from bpython.repl import Repl as BpythonRepl, SourceNotFound -from bpython.config import Struct, loadini, default_config_path, \ - getpreferredencoding +from bpython.config import (Struct, loadini, default_config_path, + getpreferredencoding) from bpython.formatter import BPythonFormatter from bpython import autocomplete, importcompletion from bpython.translations import _ @@ -38,7 +38,7 @@ from bpython.pager import get_pager_command from bpython.curtsiesfrontend import replpainter as paint -from bpython.curtsiesfrontend import sitefix; sitefix.monkeypatch_quit() +from bpython.curtsiesfrontend import sitefix from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler from bpython.curtsiesfrontend.interaction import StatusBar @@ -47,10 +47,8 @@ from bpython.curtsiesfrontend.parse import parse as bpythonparse from bpython.curtsiesfrontend.parse import func_for_letter, color_for_letter from bpython.curtsiesfrontend.preprocess import preprocess -from bpython.curtsiesfrontend.interpreter import Interp, \ - code_finished_will_parse - -#TODO other autocomplete modes (also fix in other bpython implementations) +from bpython.curtsiesfrontend.interpreter import (Interp, + code_finished_will_parse) from curtsies.configfile_keynames import keymap as key_dispatch @@ -62,20 +60,22 @@ HELP_MESSAGE = u""" Thanks for using bpython! -See http://bpython-interpreter.org/ for info, http://docs.bpython-interpreter.org/ for docs, and https://github.com/bpython/bpython for source. +See http://bpython-interpreter.org/ for more information and +http://docs.bpython-interpreter.org/ for docs. Please report issues at https://github.com/bpython/bpython/issues +Features: Try using undo ({config.undo_key})! -Edit the current line ({config.edit_current_block_key}) or the entire session ({config.external_editor_key}) in an external editor! (currently {config.editor}) +Edit the current line ({config.edit_current_block_key}) or the entire session ({config.external_editor_key}) in an external editor. (currently {config.editor}) Save sessions ({config.save_key}) or post them to pastebins ({config.pastebin_key})! Current pastebin helper: {config.pastebin_helper} -Re-execute the current session and reload all modules ({config.reimport_key}) to test out changes to a module! -Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute the current session when a module you've imported is modified! +Reload all modules and rerun session ({config.reimport_key}) to test out changes to a module. +Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute the current session when a module you've imported is modified. -Use bpython-curtsies -i your_script.py to run a file in interactive mode (interpreter in namespace of script). -Use bpython-curtsies -t your_script.py to paste in the contents of a file, as though you typed them. +bpython -i your_script.py runs a file in interactive mode +bpython -t your_script.py pastes the contents of a file into the session -Use a config file at {config_file_location} to customize keys and behavior of bpython. -You can customize which pastebin helper to use and which external editor to use. +A config file at {config_file_location} customizes keys and behavior of bpython. +You can also set which pastebin helper and which external editor to use. See {example_config_url} for an example config file. Press {config.edit_config_key} to edit this config file. """ @@ -86,11 +86,14 @@ class FakeStdin(object): - """Stdin object user code references so sys.stdin.read() asked user for interactive input""" + """The stdin object user code will reference + + In user code, sys.stdin.read() asks the user for interactive input, + so this class returns control to the UI to get that input.""" def __init__(self, coderunner, repl, configured_edit_keys=None): self.coderunner = coderunner self.repl = repl - self.has_focus = False # whether FakeStdin receives keypress events + self.has_focus = False # whether FakeStdin receives keypress events self.current_line = '' self.cursor_offset = 0 self.old_num_lines = 0 @@ -147,8 +150,10 @@ def process_event(self, e): self.repl.send_to_stdin(self.current_line) def add_input_character(self, e): - if e == '': e = ' ' - if e.startswith('<') and e.endswith('>'): return + if e == '': + e = ' ' + if e.startswith('<') and e.endswith('>'): + return assert len(e) == 1, 'added multiple characters: %r' % e logger.debug('adding normal char %r to current line', e) @@ -187,7 +192,8 @@ def write(self, value): def encoding(self): return 'UTF8' - #TODO write a read() method + # TODO write a read() method? + class ReevaluateFakeStdin(object): """Stdin mock used during reevaluation (undo) so raw_inputs don't have to @@ -196,6 +202,7 @@ def __init__(self, fakestdin, repl): self.fakestdin = fakestdin self.repl = repl self.readline_results = fakestdin.readline_results[:] + def readline(self): if self.readline_results: value = self.readline_results.pop(0) @@ -204,6 +211,7 @@ def readline(self): self.repl.send_to_stdout(value) return value + class Repl(BpythonRepl): """Python Repl @@ -224,7 +232,6 @@ class Repl(BpythonRepl): received is a window resize event, this works fine. """ - ## initialization, cleanup def __init__(self, locals_=None, config=None, @@ -232,7 +239,7 @@ def __init__(self, schedule_refresh=lambda when=0: None, request_reload=lambda desc: None, request_undo=lambda n=1: None, - get_term_hw=lambda:(50, 10), + get_term_hw=lambda: (50, 10), get_cursor_vertical_diff=lambda: 0, banner=None, interp=None, @@ -376,6 +383,7 @@ def __enter__(self): if self.watcher: # for reading modules if they fail to load old_module_locations = {} + @functools.wraps(self.orig_import) def new_import(name, globals={}, locals={}, fromlist=[], level=-1): try: @@ -393,6 +401,7 @@ def new_import(name, globals={}, locals={}, fromlist=[], level=-1): return m __builtins__['__import__'] = new_import + sitefix.monkeypatch_quit() return self def __exit__(self, *args): @@ -732,11 +741,14 @@ def send_current_block_to_external_editor(self, filename=None): self.cursor_offset = len(self.current_line) def send_session_to_external_editor(self, filename=None): - for_editor = u'### current bpython session - file will be reevaluated, ### lines will not be run\n' - for_editor += u'\n'.join(line[len(self.ps1):] if line.startswith(self.ps1) else - (line[len(self.ps2):] if line.startswith(self.ps2) else - '### '+line) - for line in self.getstdout().split('\n')) + for_editor = (u"### current bpython session - file will be " + u"reevaluated, ### lines will not be run\n'") + for_editor += u'\n'.join(line[len(self.ps1):] + if line.startswith(self.ps1) else + line[len(self.ps2):] + if line.startswith(self.ps2) else + '### '+line + for line in self.getstdout().split('\n')) text = self.send_to_external_editor(for_editor) lines = text.split('\n') from_editor = [line for line in lines if line[:4] != '### '] From 24487dd272e4a3c2628a3be52b2af2d5409e373f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 14:12:00 +0100 Subject: [PATCH 0400/1650] PEP-8 Signed-off-by: Sebastian Ramacher --- setup.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/setup.py b/setup.py index c4d3acccf..646a20304 100755 --- a/setup.py +++ b/setup.py @@ -68,6 +68,7 @@ vf.write('# Auto-generated file, do not edit!\n') vf.write('__version__ = \'%s\'\n' % (version, )) + class install(_install): """Force install to run build target.""" @@ -174,9 +175,9 @@ def initialize_options(self): ] extras_require = { - 'urwid' : ['urwid'], - 'watch' : ['watchdog'], - 'jedi' : ['jedi'], + 'urwid': ['urwid'], + 'watch': ['watchdog'], + 'jedi': ['jedi'], } packages = [ @@ -220,27 +221,27 @@ def initialize_options(self): setup( name="bpython", - version = version, - author = "Bob Farrell, Andreas Stuehrk et al.", - author_email = "robertanthonyfarrell@gmail.com", - description = "Fancy Interface to the Python Interpreter", - license = "MIT/X", - url = "http://www.bpython-interpreter.org/", - long_description = """bpython is a fancy interface to the Python + version=version, + author="Bob Farrell, Andreas Stuehrk et al.", + author_email="robertanthonyfarrell@gmail.com", + description="Fancy Interface to the Python Interpreter", + license="MIT/X", + url="http://www.bpython-interpreter.org/", + long_description="""bpython is a fancy interface to the Python interpreter for Unix-like operating systems.""", - install_requires = install_requires, - extras_require = extras_require, - tests_require = tests_require, - packages = packages, - data_files = data_files, - package_data = { + install_requires=install_requires, + extras_require=extras_require, + tests_require=tests_require, + packages=packages, + data_files=data_files, + package_data={ 'bpython': ['sample-config'], 'bpython.translations': mo_files, 'bpython.test': ['test.config', 'test.theme'] }, - entry_points = entry_points, - cmdclass = cmdclass, - test_suite = 'bpython.test' + entry_points=entry_points, + cmdclass=cmdclass, + test_suite='bpython.test' ) # vim: fileencoding=utf-8 sw=4 ts=4 sts=4 ai et sta From 9647c2606849b7c798880bf86ef1bed599e166bc Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 7 Feb 2015 11:50:39 -0500 Subject: [PATCH 0401/1650] pep8 curtsies.repl Oh the tyrany of the tiny terminal! I Wish I'd used shorter variable names from the start. --- bpython/curtsiesfrontend/repl.py | 278 ++++++++++++++++++------------- 1 file changed, 165 insertions(+), 113 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3c6ade723..ec854a1b9 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -252,13 +252,20 @@ def __init__(self, config is a bpython config.Struct with config attributes request_refresh is a function that will be called when the Repl wants to refresh the display, but wants control returned to it - afterwards Takes as a kwarg when= which is when to fire + afterwards + schedule_refresh is the same, but takes as a kwarg when= of when to + fire. Scheduled refreshes interrupt waiting for keyboard input + request_reload is like request_refresh, but for a different event + request_undo is like reload, but for a different event get_term_hw is a function that returns the current width and height of the terminal get_cursor_vertical_diff is a function that returns how the cursor moved due to a window size change banner is a string to display briefly in the status bar - interp is an interpreter to use + interp is an interpreter instance to use + original terminal state, useful for shelling out with normal terminal + on_suspend will be called on sigtstp + after_suspend will be called when process foregrounded after suspend """ logger.debug("starting init") @@ -287,18 +294,21 @@ def __init__(self, self.reevaluating = False self.fake_refresh_requested = False + def smarter_request_refresh(): if self.reevaluating or self.paste_mode: self.fake_refresh_requested = True else: request_refresh() self.request_refresh = smarter_request_refresh + def smarter_schedule_refresh(when='now'): if self.reevaluating or self.paste_mode: self.fake_refresh_requested = True else: schedule_refresh(when=when) self.schedule_refresh = smarter_schedule_refresh + def smarter_request_reload(files_modified=()): if self.watching_files: request_reload(files_modified=files_modified) @@ -315,27 +325,40 @@ def smarter_request_reload(files_modified=()): self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch) logger.debug("starting parent init") super(Repl, self).__init__(interp, config) - # TODO bring together all interactive stuff - including current - # directory in path? + self.formatter = BPythonFormatter(config.color_scheme) - self.interact = self.status_bar # overwriting what bpython.Repl put there - # interact is called to interact with the status bar, - # so we're just using the same object - self._current_line = '' # line currently being edited, without ps1 (usually '>>> ') - self.current_stdouterr_line = '' # current line of output - stdout and stdin go here - self.display_lines = [] # lines separated whenever logical line - # length goes over what the terminal width - # was at the time of original output - self.history = [] # this is every line that's been executed; - # it gets smaller on rewind - self.display_buffer = [] # formatted version of lines in the buffer - # kept around so we can unhighlight parens - # using self.reprint_line as called by - # bpython.Repl - self.scroll_offset = 0 # how many times display has been scrolled down - # because there wasn't room to display everything - self._cursor_offset = 0 # from the left, 0 means first char - self.orig_tcattrs = orig_tcattrs # useful for shelling out with normal terminal + + # overwriting what bpython.Repl put there + # interact is called to interact with the status bar, + # so we're just using the same object + self.interact = self.status_bar + + # line currently being edited, without ps1 (usually '>>> ') + self._current_line = '' + + # current line of output - stdout and stdin go here + self.current_stdouterr_line = '' + + # lines separated whenever logical line + # length goes over what the terminal width + # was at the time of original output + self.display_lines = [] + + # this is every line that's been executed; it gets smaller on rewind + self.history = [] + + # formatted version of lines in the buffer kept around so we can + # unhighlight parens using self.reprint_line as called by bpython.Repl + self.display_buffer = [] + + # how many times display has been scrolled down + # because there wasn't room to display everything + self.scroll_offset = 0 + + # from the left, 0 means first char + self._cursor_offset = 0 + + self.orig_tcattrs = orig_tcattrs self.on_suspend = on_suspend self.after_suspend = after_suspend @@ -344,18 +367,31 @@ def smarter_request_reload(files_modified=()): self.stderr = FakeOutput(self.coderunner, self.send_to_stderr) self.stdin = FakeStdin(self.coderunner, self, self.edit_keys) - self.request_paint_to_clear_screen = False # next paint should clear screen - self.inconsistent_history = False # offscreen command yields different result from history - self.history_already_messed_up = False # history error message displayed - self.last_events = [None] * 50 # some commands act differently based on the prev event - # this list doesn't include instances of event.Event, - # only keypress-type events (no refresh screen events etc.) - self.presentation_mode = False # displays prev events in a column on the right hand side - self.paste_mode = False # currently processing a paste event - self.current_match = None # currently tab-selected autocompletion suggestion - self.list_win_visible = False # whether the infobox (suggestions, docstring) is visible - self.watching_files = False # auto reloading turned on - self.incremental_search_mode = None # 'reverse_incremental_search' and 'incremental_search' + # next paint should clear screen + self.request_paint_to_clear_screen = False + + # offscreen command yields results different from scrollback bufffer + self.inconsistent_history = False + + # history error message has already been displayed + self.history_already_messed_up = False + + # some commands act differently based on the prev event + # this list doesn't include instances of event.Event, + # only keypress-type events (no refresh screen events etc.) + self.last_events = [None] * 50 + + # displays prev events in a column on the right hand side + self.presentation_mode = False + + self.paste_mode = False + self.current_match = None + self.list_win_visible = False + self.watching_files = False # whether auto reloading active + + # 'reverse_incremental_search', 'incremental_search' or None + self.incremental_search_mode = None + self.incremental_search_target = '' self.original_modules = sys.modules.keys() @@ -436,7 +472,7 @@ def clean_up_current_line_for_exit(self): self.cursor_offset = -1 self.unhighlight_paren() - ## Event handling + # Event handling def process_event(self, e): """Returns True if shutting down, otherwise returns None. Mostly mutates state of Repl object""" @@ -514,8 +550,10 @@ def process_control_event(self, e): def process_key_event(self, e): # To find the curtsies name for a keypress, try # python -m curtsies.events - if self.status_bar.has_focus: return self.status_bar.process_event(e) - if self.stdin.has_focus: return self.stdin.process_event(e) + if self.status_bar.has_focus: + return self.status_bar.process_event(e) + if self.stdin.has_focus: + return self.stdin.process_event(e) if (e in ("", '') and self.config.curtsies_right_arrow_completion and @@ -536,15 +574,16 @@ def process_key_event(self, e): elif e in ("",): self.incremental_search() elif (e in ("",) + key_dispatch[self.config.backspace_key] - and self.incremental_search_mode): + and self.incremental_search_mode): self.add_to_incremental_search(self, backspace=True) elif e in self.edit_keys.cut_buffer_edits: self.readline_kill(e) elif e in self.edit_keys.simple_edits: - self.cursor_offset, self.current_line = self.edit_keys.call(e, - cursor_offset=self.cursor_offset, - line=self.current_line, - cut_buffer=self.cut_buffer) + self.cursor_offset, self.current_line = self.edit_keys.call( + e, + cursor_offset=self.cursor_offset, + line=self.current_line, + cut_buffer=self.cut_buffer) elif e in key_dispatch[self.config.cut_to_buffer_key]: self.cut_to_buffer() elif e in key_dispatch[self.config.reimport_key]: @@ -563,15 +602,15 @@ def process_key_event(self, e): raise SystemExit() elif e in ("\n", "\r", "", "", ""): self.on_enter() - elif e == '': # tab + elif e == '': # tab self.on_tab() elif e in ("",): self.on_tab(back=True) - elif e in key_dispatch[self.config.undo_key]: #ctrl-r for undo + elif e in key_dispatch[self.config.undo_key]: # ctrl-r for undo self.prompt_undo() - elif e in key_dispatch[self.config.save_key]: # ctrl-s for save + elif e in key_dispatch[self.config.save_key]: # ctrl-s for save greenlet.greenlet(self.write2file).switch() - elif e in key_dispatch[self.config.pastebin_key]: # F8 for pastebin + elif e in key_dispatch[self.config.pastebin_key]: # F8 for pastebin greenlet.greenlet(self.pastebin).switch() elif e in key_dispatch[self.config.copy_clipboard_key]: greenlet.greenlet(self.copy2clipboard).switch() @@ -582,7 +621,7 @@ def process_key_event(self, e): # TODO add PAD keys hack as in bpython.cli elif e in key_dispatch[self.config.edit_current_block_key]: self.send_current_block_to_external_editor() - elif e in [""]: #ESC + elif e in [""]: #ESC self.incremental_search_mode = None elif e in [""]: self.add_normal_character(' ') @@ -599,26 +638,31 @@ def last_word(line): line = self.current_line self._set_current_line(line[:len(line)-len(previous_word)] + word, reset_rl_history=False) - self._set_cursor_offset(self.cursor_offset-len(previous_word) + len(word), - reset_rl_history=False) + self._set_cursor_offset( + self.cursor_offset-len(previous_word) + len(word), + reset_rl_history=False) def incremental_search(self, reverse=False, include_current=False): - if self.incremental_search_mode == None: + if self.incremental_search_mode is None: self.rl_history.enter(self.current_line) self.incremental_search_target = '' else: if self.incremental_search_target: - line = (self.rl_history.back(False, search=True, - target=self.incremental_search_target, - include_current=include_current) - if reverse else - self.rl_history.forward(False, search=True, - target=self.incremental_search_target, - include_current=include_current)) + line = (self.rl_history.back( + False, search=True, + target=self.incremental_search_target, + include_current=include_current) + if reverse else + self.rl_history.forward( + False, search=True, + target=self.incremental_search_target, + include_current=include_current)) self._set_current_line(line, - reset_rl_history=False, clear_special_mode=False) + reset_rl_history=False, + clear_special_mode=False) self._set_cursor_offset(len(self.current_line), - reset_rl_history=False, clear_special_mode=False) + reset_rl_history=False, + clear_special_mode=False) if reverse: self.incremental_search_mode = 'reverse_incremental_search' else: @@ -628,15 +672,19 @@ def readline_kill(self, e): func = self.edit_keys[e] self.cursor_offset, self.current_line, cut = func(self.cursor_offset, self.current_line) - if self.last_events[-2] == e: # consecutive kill commands are cumulative - if func.kills == 'ahead': self.cut_buffer += cut - elif func.kills == 'behind': self.cut_buffer = cut + self.cut_buffer - else: raise ValueError("cut had value other than 'ahead' or 'behind'") + if self.last_events[-2] == e: # consecutive kill commands accumulative + if func.kills == 'ahead': + self.cut_buffer += cut + elif func.kills == 'behind': + self.cut_buffer = cut + self.cut_buffer + else: + raise ValueError("cut value other than 'ahead' or 'behind'") else: self.cut_buffer = cut def on_enter(self, insert_into_history=True): - self._set_cursor_offset(-1, update_completion=False) # so the cursor isn't touching a paren + # so the cursor isn't touching a paren TODO: necessary? + self._set_cursor_offset(-1, update_completion=False) self.history.append(self.current_line) self.push(self.current_line, insert_into_history=insert_into_history) @@ -647,27 +695,24 @@ def on_tab(self, back=False): Does one of the following: 1) add space to move up to the next %4==0 column - 2) complete the current word with characters common to all completions and + 2) complete the current word with characters common to all completions 3) select the first or last match 4) select the next or previous match if already have a match """ def only_whitespace_left_of_cursor(): - """returns true if all characters on current line before cursor are whitespace""" + """returns true if all characters before cursor are whitespace""" return not self.current_line[:self.cursor_offset].strip() - logger.debug('self.matches_iter.matches: %r', self.matches_iter.matches) + logger.debug('self.matches_iter.matches:%r', self.matches_iter.matches) if only_whitespace_left_of_cursor(): - front_white = (len(self.current_line[:self.cursor_offset]) - - len(self.current_line[:self.cursor_offset].lstrip())) - to_add = 4 - (front_white % self.config.tab_length) + front_ws = (len(self.current_line[:self.cursor_offset]) - + len(self.current_line[:self.cursor_offset].lstrip())) + to_add = 4 - (front_ws % self.config.tab_length) for unused in range(to_add): self.add_normal_character(' ') return - #if not self.matches_iter.candidate_selected: - # self.list_win_visible = self.complete(tab=True) - # run complete() if we don't already have matches if len(self.matches_iter.matches) == 0: self.list_win_visible = self.complete(tab=True) @@ -680,8 +725,8 @@ def only_whitespace_left_of_cursor(): self.list_win_visible = self.complete() elif self.matches_iter.matches: - self.current_match = back and self.matches_iter.previous() \ - or next(self.matches_iter) + self.current_match = (back and self.matches_iter.previous() + or next(self.matches_iter)) self._cursor_offset, self._current_line = self.matches_iter.cur_line() # using _current_line so we don't trigger a completion reset self.list_win_visible = True @@ -690,7 +735,8 @@ def on_control_d(self): if self.current_line == '': raise SystemExit() else: - self.current_line = self.current_line[:self.cursor_offset] + self.current_line[self.cursor_offset+1:] + self.current_line = (self.current_line[:self.cursor_offset] + + self.current_line[self.cursor_offset+1:]) def cut_to_buffer(self): self.cut_buffer = self.current_line[self.cursor_offset:] @@ -701,28 +747,31 @@ def yank_from_buffer(self): def up_one_line(self): self.rl_history.enter(self.current_line) - self._set_current_line(tabs_to_spaces(self.rl_history.back(False, - search=self.config.curtsies_right_arrow_completion)), + self._set_current_line(tabs_to_spaces(self.rl_history.back( + False, + search=self.config.curtsies_right_arrow_completion)), update_completion=False, reset_rl_history=False) self._set_cursor_offset(len(self.current_line), reset_rl_history=False) def down_one_line(self): self.rl_history.enter(self.current_line) - self._set_current_line(tabs_to_spaces(self.rl_history.forward(False, - search=self.config.curtsies_right_arrow_completion)), + self._set_current_line(tabs_to_spaces(self.rl_history.forward( + False, + search=self.config.curtsies_right_arrow_completion)), update_completion=False, reset_rl_history=False) self._set_cursor_offset(len(self.current_line), reset_rl_history=False) def process_simple_keypress(self, e): - if e in (u"", u"", u"", u"\n", u"\r"): # '\n' necessary for pastes + # '\n' needed for pastes + if e in (u"", u"", u"", u"\n", u"\r"): self.on_enter() while self.fake_refresh_requested: self.fake_refresh_requested = False self.process_event(bpythonevents.RefreshRequestEvent()) elif isinstance(e, events.Event): - pass # ignore events + pass # ignore events elif e == '': self.add_normal_character(' ') else: @@ -759,14 +808,15 @@ def send_session_to_external_editor(self, filename=None): self.cursor_offset = len(self.current_line) def clear_modules_and_reevaluate(self): - if self.watcher: self.watcher.reset() + if self.watcher: + self.watcher.reset() cursor, line = self.cursor_offset, self.current_line for modname in sys.modules.keys(): if modname not in self.original_modules: del sys.modules[modname] self.reevaluate(insert_into_history=True) self.cursor_offset, self.current_line = cursor, line - self.status_bar.message(_('Reloaded at %s by user.') % \ + self.status_bar.message(_('Reloaded at %s by user.') % (time.strftime('%X'), )) def toggle_file_watch(self): @@ -785,7 +835,7 @@ def toggle_file_watch(self): self.status_bar.message(_('Auto-reloading not available because ' 'watchdog not installed.')) - ## Handler Helpers + # Handler Helpers def add_normal_character(self, char): if len(char) > 1 or is_nop(char): return @@ -795,11 +845,12 @@ def add_normal_character(self, char): self._set_current_line((self.current_line[:self.cursor_offset] + char + self.current_line[self.cursor_offset:]), - update_completion=False, - reset_rl_history=False, - clear_special_mode=False) + update_completion=False, + reset_rl_history=False, + clear_special_mode=False) self.cursor_offset += 1 - if self.config.cli_trim_prompts and self.current_line.startswith(self.ps1): + if (self.config.cli_trim_prompts and + self.current_line.startswith(self.ps1)): self.current_line = self.current_line[4:] self.cursor_offset = max(0, self.cursor_offset - 4) @@ -823,14 +874,14 @@ def add_to_incremental_search(self, char=None, backspace=False): def update_completion(self, tab=False): """Update visible docstring and matches, and possibly hide/show completion box""" - #Update autocomplete info; self.matches_iter and self.argspec - #Should be called whenever the completion box might need to appear / dissapear - #when current line or cursor offset changes, unless via selecting a match + # Update autocomplete info; self.matches_iter and self.argspec + # Should be called whenever the completion box might need to appear / dissapear + # when current line or cursor offset changes, unless via selecting a match self.current_match = None self.list_win_visible = BpythonRepl.complete(self, tab) def predicted_indent(self, line): - #TODO get rid of this! It's repeated code! Combine with Repl. + # TODO get rid of this! It's repeated code! Combine with Repl. logger.debug('line is %r', line) indent = len(re.match(r'[ ]*', line).group()) if line.endswith(':'): @@ -852,7 +903,6 @@ def push(self, line, insert_into_history=True): else: self.saved_indent = self.predicted_indent(line) - #current line not added to display buffer if quitting #TODO I don't understand this comment if self.config.syntax: display_line = bpythonparse(format(self.tokenize(line), self.formatter)) # careful: self.tokenize requires that the line not be in self.buffer yet! @@ -904,7 +954,7 @@ def run_code_and_maybe_finish(self, for_code=None): self.cursor_offset = len(self.current_line) def keyboard_interrupt(self): - #TODO factor out the common cleanup from running a line + # TODO factor out the common cleanup from running a line self.cursor_offset = -1 self.unhighlight_paren() self.display_lines.extend(self.display_buffer_lines) @@ -959,21 +1009,15 @@ def send_to_stderr(self, error): if lines[-1]: self.current_stdouterr_line += lines[-1] self.display_lines.extend(sum((paint.display_linize(line, self.width, - blank_line=True) for - line in lines[:-1]), [])) + blank_line=True) + for line in lines[:-1]), [])) def send_to_stdin(self, line): if line.endswith('\n'): self.display_lines.extend(paint.display_linize(self.current_output_line, self.width)) self.current_output_line = '' - #self.display_lines = self.display_lines[:len(self.display_lines) - self.stdin.old_num_lines] - #lines = paint.display_linize(line, self.width) - #self.stdin.old_num_lines = len(lines) - #self.display_lines.extend(paint.display_linize(line, self.width)) - pass - - ## formatting, output + # formatting, output @property def done(self): """Whether the last block is complete - which prompt to use, ps1 or ps2""" @@ -1009,8 +1053,8 @@ def display_buffer_lines(self): lines = [] for display_line in self.display_buffer: display_line = (func_for_letter(self.config.color_scheme['prompt_more'])(self.ps2) - if lines else - func_for_letter(self.config.color_scheme['prompt'])(self.ps1)) + display_line + if lines else + func_for_letter(self.config.color_scheme['prompt'])(self.ps1)) + display_line for line in paint.display_linize(display_line, self.width): lines.append(line) return lines @@ -1252,7 +1296,8 @@ def dumb_input(self, requested_refreshes=[]): elif c in '\\': c = '' elif c in '|': - def r(): raise Exception('errors in other threads should look like this') + def r(): + raise Exception('errors in other threads should look like this') t = threading.Thread(target=r) t.daemon = True t.start() @@ -1272,7 +1317,8 @@ def __repr__(self): def _get_current_line(self): return self._current_line - def _set_current_line(self, line, update_completion=True, reset_rl_history=True, clear_special_mode=True): + def _set_current_line(self, line, update_completion=True, + reset_rl_history=True, clear_special_mode=True): if self._current_line == line: return self._current_line = line @@ -1289,7 +1335,8 @@ def _set_current_line(self, line, update_completion=True, reset_rl_history=True, def _get_cursor_offset(self): return self._cursor_offset - def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=False, clear_special_mode=True): + def _set_cursor_offset(self, offset, update_completion=True, + reset_rl_history=False, clear_special_mode=True): if self._cursor_offset == offset: return if reset_rl_history: @@ -1351,7 +1398,8 @@ def prompt_for_undo(): def reevaluate(self, insert_into_history=False): """bpython.Repl.undo calls this""" - if self.watcher: self.watcher.reset() + if self.watcher: + self.watcher.reset() old_logical_lines = self.history old_display_lines = self.display_lines self.history = [] @@ -1446,8 +1494,7 @@ def version_help_text(self): ('using curtsies version %s' % curtsies.__version__) + '\n' + HELP_MESSAGE.format(config_file_location=default_config_path(), example_config_url='https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config', - config=self.config) - ) + config=self.config)) def key_help_text(self): NOT_IMPLEMENTED = ('suspend', 'cut to buffer', 'search', 'last output', @@ -1468,12 +1515,15 @@ def key_help_text(self): max_func = max(len(func) for func, key in pairs) return '\n'.join('%s : %s' % (func.rjust(max_func), key) for func, key in pairs) + def is_nop(char): return unicodedata.category(unicode(char)) == 'Cc' + def tabs_to_spaces(line): return line.replace('\t', ' ') + def compress_paste_event(paste_event): """If all events in a paste event are identical and not simple characters, returns one of them @@ -1492,6 +1542,7 @@ def compress_paste_event(paste_event): else: return None + def just_simple_events(event_list): simple_events = [] for e in event_list: @@ -1499,7 +1550,7 @@ def just_simple_events(event_list): if e in (u"", u"", u"", u"\n", u"\r"): simple_events.append(u'\n') elif isinstance(e, events.Event): - pass # ignore events + pass # ignore events elif e == '': simple_events.append(' ') else: @@ -1510,6 +1561,7 @@ def just_simple_events(event_list): # TODO this needs some work to function again and be useful for embedding def simple_repl(): refreshes = [] + def request_refresh(): refreshes.append(1) with Repl(request_refresh=request_refresh) as r: From ff1d017635bd8e01cf47605b38101bbeebf70695 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 17:55:59 +0100 Subject: [PATCH 0402/1650] Add encoding to constructor Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/interpreter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 1945e1bac..37fd5a94b 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -64,7 +64,7 @@ def format(self, tokensource, outfile): class Interp(ReplInterpreter): - def __init__(self, locals=None): + def __init__(self, locals=None, encoding=None): """Constructor. The optional 'locals' argument specifies the dictionary in @@ -77,7 +77,9 @@ def __init__(self, locals=None): """ if locals is None: locals = {"__name__": "__console__", "__doc__": None} - ReplInterpreter.__init__(self, locals, getpreferredencoding()) + if encoding is None: + encoding = getpreferredencoding() + ReplInterpreter.__init__(self, locals, encoding) self.locals = locals self.compile = CommandCompiler() From 64772c0289b6048daaf15d9c24f493740ef3247b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 17:56:11 +0100 Subject: [PATCH 0403/1650] Remove runsource overwrite Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/interpreter.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 37fd5a94b..fea3fddcc 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -94,16 +94,6 @@ def writetb(self, lines): self.format(tbtext, lexer) # TODO for tracebacks get_lexer_by_name("pytb", stripall=True) - if not py3: - def runsource(self, source, filename="", symbol="single", - encode=True): - # TODO: write a test for and implement encoding - with self.timer: - if encode: - source = '#\n%s' % (source,) # dummy line so linenos match - return code.InteractiveInterpreter.runsource(self, source, - filename, symbol) - def format(self, tbtext, lexer): traceback_informative_formatter = BPythonFormatter(default_colors) traceback_code_formatter = BPythonFormatter({Token: ('d')}) From 9542b0c3738d30992f55607508ccbd9f6400578d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 17:56:20 +0100 Subject: [PATCH 0404/1650] Properly encode source Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 95619b5b3..02d9710b7 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -108,8 +108,8 @@ def runsource(self, source, filename='', symbol='single', encode=True): with self.timer: if encode: - source = '# coding: %s\n%s' % ( - self.encoding, source.encode(self.encoding)) + source = u'# coding: %s\n%s' % (self.encoding, source) + source = source.encode(self.encoding) return code.InteractiveInterpreter.runsource(self, source, filename, symbol) From e4c3d4de96df62b062d637f17b4a727dc932f846 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 17:56:51 +0100 Subject: [PATCH 0405/1650] Simplify tests and enable all of them Signed-off-by: Sebastian Ramacher --- bpython/test/test_interpreter.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 97c7d596f..a7fcdd118 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + from __future__ import unicode_literals try: @@ -57,40 +59,36 @@ def g(): @unittest.skipIf(py3, "runsource() accepts only unicode in Python 3") def test_runsource_bytes(self): - i = interpreter.Interp() - i.encoding = 'latin-1' + i = interpreter.Interp(encoding='latin-1') - i.runsource(b"a = b'\xfe'") + i.runsource("a = b'\xfe'".encode('latin-1'), encode=False) self.assertIsInstance(i.locals['a'], str) self.assertEqual(i.locals['a'], b"\xfe") - i.runsource(b"b = u'\xfe'") + i.runsource("b = u'\xfe'".encode('latin-1'), encode=False) self.assertIsInstance(i.locals['b'], unicode) - self.assertEqual(i.locals['b'], u"\xfe") + self.assertEqual(i.locals['b'], "\xfe") @unittest.skipUnless(py3, "Only a syntax error in Python 3") @mock.patch.object(interpreter.Interp, 'showsyntaxerror') def test_runsource_bytes_over_128_syntax_error(self): - i = interpreter.Interp() - i.encoding = 'latin-1' + i = interpreter.Interp(encoding='latin-1') - i.runsource(u"a = b'\xfe'") + i.runsource("a = b'\xfe'", encode=True) i.showsyntaxerror.assert_called_with() - @unittest.skip("Is the goal of encoding to get this to work?") - @unittest.skipIf(py3, "bytes 128-255 only permitted in Py 2") + @unittest.skipIf(py3, "encode is Python 2 only") def test_runsource_bytes_over_128_syntax_error(self): - i = interpreter.Interp() - i.encoding = 'latin-1' + i = interpreter.Interp(encoding='latin-1') - i.runsource(u"a = b'\xfe'") + i.runsource("a = b'\xfe'", encode=True) self.assertIsInstance(i.locals['a'], type(b'')) self.assertEqual(i.locals['a'], b"\xfe") + @unittest.skipIf(py3, "encode is Python 2 only") def test_runsource_unicode(self): - i = interpreter.Interp() - i.encoding = 'latin-1' + i = interpreter.Interp(encoding='latin-1') - i.runsource(u"a = u'\xfe'") + i.runsource("a = u'\xfe'", encode=True) self.assertIsInstance(i.locals['a'], type(u'')) self.assertEqual(i.locals['a'], u"\xfe") From d0bc25d210396bc67937a4503742d8237f7ed055 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 18:37:59 +0100 Subject: [PATCH 0406/1650] Try to read encoding of PYTHONSTARTUP file first, and then read it with correct encoding Signed-off-by: Sebastian Ramacher --- bpython/inspection.py | 14 ++++++++++++++ bpython/repl.py | 10 ++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index e94edd8ed..cdcea8949 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -24,9 +24,11 @@ from __future__ import with_statement import collections import inspect +import io import keyword import pydoc import types +from six.moves import range from pygments.token import Token @@ -280,6 +282,7 @@ def is_callable(obj): def get_encoding(obj): + """Try to obtain encoding information of the source of an object.""" for line in inspect.findsource(obj)[0][:2]: m = get_encoding_re.search(line) if m: @@ -287,6 +290,17 @@ def get_encoding(obj): return 'ascii' +def get_encoding_file(fname): + """Try to obtain encoding information from a Python source file.""" + with io.open(fname, 'rt', encoding='ascii', errors='ignore') as f: + for unused in range(2): + line = f.readline() + match = get_encoding_re.search(line) + if match: + return match.group(1) + return 'ascii' + + def get_source_unicode(obj): """Returns a decoded source of object""" if py3: diff --git a/bpython/repl.py b/bpython/repl.py index 02d9710b7..aed6a31ce 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -24,6 +24,7 @@ import code import errno import inspect +import io import os import pkgutil import pydoc @@ -406,12 +407,9 @@ def startup(self): """ filename = os.environ.get('PYTHONSTARTUP') if filename: - with open(filename, 'r') as f: - if py3: - self.interp.runsource(f.read(), filename, 'exec') - else: - self.interp.runsource(f.read(), filename, 'exec', - encode=False) + encoding = inspection.get_encoding_file(filename) + with io.open(filename, 'rt', encoding=encoding) as f: + self.interp.runsource(f.read(), filename, 'exec') def current_string(self, concatenate=False): """If the line ends in a string get it, otherwise return ''""" From b5d779012fcf8fc3f438432b731313ff038373b2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 18:52:43 +0100 Subject: [PATCH 0407/1650] Test differently encoded startup files Signed-off-by: Sebastian Ramacher --- bpython/test/test_curtsies_repl.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 5eb9798e4..5c4cbec36 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -1,10 +1,11 @@ -# coding: utf8 +# coding: utf-8 from __future__ import unicode_literals import code import os import sys import tempfile +import io from contextlib import contextmanager from six.moves import StringIO @@ -313,13 +314,31 @@ class TestCurtsiesStartup(TestCase): def setUp(self): self.repl = create_repl() - os.environ['PYTHONSTARTUP'] = 'file' + self.startupfile = tempfile.NamedTemporaryFile() + self.startupfile.__enter__() + os.environ['PYTHONSTARTUP'] = self.startupfile.name def tearDown(self): + self.startupfile.__exit__(None, None, None) del os.environ['PYTHONSTARTUP'] - @mock.patch(builtin_target(open), mock.mock_open(read_data='a = 1\n')) - def test_startup_event(self): + def write_startup_file(self, encoding, write_encoding=True): + with io.open(self.startupfile.name, mode='wt', + encoding=encoding) as f: + if write_encoding: + f.write('# coding: ') + f.write(encoding) + f.write('\n') + f.write('from __future__ import unicode_literals\n') + f.write('a = "äöü"\n') + + def test_startup_event_utf8(self): + self.write_startup_file('utf-8') + self.repl.process_event(bpythonevents.RunStartupFileEvent()) + self.assertIn('a', self.repl.interp.locals) + + def test_startup_event_utf8(self): + self.write_startup_file('latin-1') self.repl.process_event(bpythonevents.RunStartupFileEvent()) self.assertIn('a', self.repl.interp.locals) From a4038cdc1fc3abfe5d51d65b5f9c9ff87a907bfd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 18:56:44 +0100 Subject: [PATCH 0408/1650] Simplify Signed-off-by: Sebastian Ramacher --- bpython/test/test_curtsies_repl.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 5c4cbec36..4fddf7d3d 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -322,15 +322,14 @@ def tearDown(self): self.startupfile.__exit__(None, None, None) del os.environ['PYTHONSTARTUP'] - def write_startup_file(self, encoding, write_encoding=True): + def write_startup_file(self, encoding): with io.open(self.startupfile.name, mode='wt', encoding=encoding) as f: - if write_encoding: - f.write('# coding: ') - f.write(encoding) - f.write('\n') - f.write('from __future__ import unicode_literals\n') - f.write('a = "äöü"\n') + f.write('# coding: ') + f.write(encoding) + f.write('\n') + f.write('from __future__ import unicode_literals\n') + f.write('a = "äöü"\n') def test_startup_event_utf8(self): self.write_startup_file('utf-8') From f555a1c40c3df6a3a021e79b1cff7589b1341216 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 19:09:49 +0100 Subject: [PATCH 0409/1650] Bump six to >= 1.4 Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 6 +++++- setup.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index c03b969c7..fa3fd1622 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -6,7 +6,11 @@ pip install setuptools if [[ $RUN == nosetests ]]; then # core dependencies - pip install pygments requests 'curtsies >=0.1.17,<0.2.0' greenlet six + pip install pygments + pip install requests + pip install 'curtsies >=0.1.17,<0.2.0' + pip install greenlet + pip install 'six >=1.4' # filewatch specific dependencies pip install watchdog # jedi specific dependencies diff --git a/setup.py b/setup.py index 646a20304..b92fe4eb9 100755 --- a/setup.py +++ b/setup.py @@ -171,7 +171,7 @@ def initialize_options(self): 'requests', 'curtsies >=0.1.17, <0.2.0', 'greenlet', - 'six' + 'six >=1.4' ] extras_require = { From 60c4c0895929d85e5a89e5ae344cb52b2f0d3613 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 19:45:20 +0100 Subject: [PATCH 0410/1650] Fix test name and use mock.patch.dict Signed-off-by: Sebastian Ramacher --- bpython/test/test_curtsies_repl.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 4fddf7d3d..8b3e6b177 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -314,17 +314,9 @@ class TestCurtsiesStartup(TestCase): def setUp(self): self.repl = create_repl() - self.startupfile = tempfile.NamedTemporaryFile() - self.startupfile.__enter__() - os.environ['PYTHONSTARTUP'] = self.startupfile.name - def tearDown(self): - self.startupfile.__exit__(None, None, None) - del os.environ['PYTHONSTARTUP'] - - def write_startup_file(self, encoding): - with io.open(self.startupfile.name, mode='wt', - encoding=encoding) as f: + def write_startup_file(self, fname, encoding): + with io.open(fname, mode='wt', encoding=encoding) as f: f.write('# coding: ') f.write(encoding) f.write('\n') @@ -332,13 +324,17 @@ def write_startup_file(self, encoding): f.write('a = "äöü"\n') def test_startup_event_utf8(self): - self.write_startup_file('utf-8') - self.repl.process_event(bpythonevents.RunStartupFileEvent()) + with tempfile.NamedTemporaryFile() as temp: + self.write_startup_file(temp.name, 'utf-8') + with mock.patch.dict('os.environ', {'PYTHONSTARTUP': temp.name}): + self.repl.process_event(bpythonevents.RunStartupFileEvent()) self.assertIn('a', self.repl.interp.locals) - def test_startup_event_utf8(self): - self.write_startup_file('latin-1') - self.repl.process_event(bpythonevents.RunStartupFileEvent()) + def test_startup_event_latin1(self): + with tempfile.NamedTemporaryFile() as temp: + self.write_startup_file(temp.name, 'latin-1') + with mock.patch.dict('os.environ', {'PYTHONSTARTUP': temp.name}): + self.repl.process_event(bpythonevents.RunStartupFileEvent()) self.assertIn('a', self.repl.interp.locals) From 6d8952c20f354aa3109bd4ecab27ab7eaac346be Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 22:02:56 +0100 Subject: [PATCH 0411/1650] Unify runsource and document its behavior Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index aed6a31ce..54a0c78c1 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -96,23 +96,19 @@ def __init__(self, locals=None, encoding=None): def reset_running_time(self): self.running_time = 0 - if py3: - - def runsource(self, source, filename="", symbol="single"): - with self.timer: - return code.InteractiveInterpreter.runsource(self, source, - filename, symbol) - - else: - - def runsource(self, source, filename='', symbol='single', - encode=True): - with self.timer: - if encode: - source = u'# coding: %s\n%s' % (self.encoding, source) - source = source.encode(self.encoding) - return code.InteractiveInterpreter.runsource(self, source, - filename, symbol) + def runsource(self, source, filename='', symbol='single', + encode=True): + """Execute Python code. + + source, filename and symbol are passed on to + code.InteractiveInterpreter.runsource. If encode is True, the source + will be encoded. On Python 3.X, encode will be ignored.""" + if not py3 and encode: + source = u'# coding: %s\n%s' % (self.encoding, source) + source = source.encode(self.encoding) + with self.timer: + return code.InteractiveInterpreter.runsource(self, source, + filename, symbol) def showsyntaxerror(self, filename=None): """Override the regular handler, the code's copied and pasted from @@ -409,7 +405,11 @@ def startup(self): if filename: encoding = inspection.get_encoding_file(filename) with io.open(filename, 'rt', encoding=encoding) as f: - self.interp.runsource(f.read(), filename, 'exec') + source = f.read() + if not py3: + # Python 2.6 and early 2.7.X need bytes. + source = source.encode(encoding) + self.interp.runsource(source, filename, 'exec', encode=False) def current_string(self, concatenate=False): """If the line ends in a string get it, otherwise return ''""" From 7587ad8dfeabcfa95af37285f7716af2eb9fdb53 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Feb 2015 22:16:00 +0100 Subject: [PATCH 0412/1650] Test for get_encoding_file Signed-off-by: Sebastian Ramacher --- bpython/test/test_inspection.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 10c0863f3..7fca0eec8 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +import os + try: import unittest2 as unittest except ImportError: @@ -102,5 +104,20 @@ def test_get_source_latin1(self): self.assertEqual(inspection.get_source_unicode(encoding_latin1.foo), foo_non_ascii) + def test_get_source_file(self): + path = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'fodder') + + encoding = inspection.get_encoding_file( + os.path.join(path, 'encoding_ascii.py')) + self.assertEqual(encoding, 'ascii') + encoding = inspection.get_encoding_file( + os.path.join(path, 'encoding_latin1.py')) + self.assertEqual(encoding, 'latin1') + encoding = inspection.get_encoding_file( + os.path.join(path, 'encoding_utf8.py')) + self.assertEqual(encoding, 'utf-8') + + if __name__ == '__main__': unittest.main() From d635494602498e1c4f25db6726c998be78d5e4f4 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 7 Feb 2015 13:09:51 -0500 Subject: [PATCH 0413/1650] PEP-8 in curtsies repl --- bpython/curtsiesfrontend/repl.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index ec854a1b9..589cf3090 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -523,7 +523,7 @@ def process_control_event(self, e): self.startup() except IOError as e: self.status_bar.message( - _('Executing PYTHONSTARTUP failed: %s') % (str(e))) + _('Executing PYTHONSTARTUP failed: %s') % (str(e))) elif isinstance(e, bpythonevents.UndoEvent): self.undo(n=e.n) @@ -621,7 +621,7 @@ def process_key_event(self, e): # TODO add PAD keys hack as in bpython.cli elif e in key_dispatch[self.config.edit_current_block_key]: self.send_current_block_to_external_editor() - elif e in [""]: #ESC + elif e in [""]: self.incremental_search_mode = None elif e in [""]: self.add_normal_character(' ') @@ -636,11 +636,11 @@ def last_word(line): previous_word = last_word(self.rl_history.entry) word = last_word(self.rl_history.back()) line = self.current_line - self._set_current_line(line[:len(line)-len(previous_word)] + word, + self._set_current_line(line[:len(line) - len(previous_word)] + word, reset_rl_history=False) self._set_cursor_offset( - self.cursor_offset-len(previous_word) + len(word), - reset_rl_history=False) + self.cursor_offset-len(previous_word) + len(word), + reset_rl_history=False) def incremental_search(self, reverse=False, include_current=False): if self.incremental_search_mode is None: @@ -870,7 +870,7 @@ def add_to_incremental_search(self, char=None, backspace=False): elif self.incremental_search_mode == 'incremental_search': self.incremental_search(include_current=True) else: - raise ValueError('add_to_incremental_search should only be called in a special mode') + raise ValueError('add_to_incremental_search not in a special mode') def update_completion(self, tab=False): """Update visible docstring and matches, and possibly hide/show completion box""" From a85dafa2b61937f55606683b7f82b67758e36d2d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 8 Feb 2015 23:13:20 -0500 Subject: [PATCH 0414/1650] bump curtsies version to fix encoding problems in pythno2.6 issue was unicode being written to stdout, now curtsies always writes bytes in Python 2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b92fe4eb9..faed25f1d 100755 --- a/setup.py +++ b/setup.py @@ -169,7 +169,7 @@ def initialize_options(self): install_requires = [ 'pygments', 'requests', - 'curtsies >=0.1.17, <0.2.0', + 'curtsies >=0.1.18, <0.2.0', 'greenlet', 'six >=1.4' ] From ee48fb3d429d4f4883c1b8ad1b5428766f4fb6db Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 9 Feb 2015 18:53:47 +0100 Subject: [PATCH 0415/1650] Update documentation Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/contributing.rst | 71 ++++++++++++++++-------------- doc/sphinx/source/index.rst | 5 --- doc/sphinx/source/releases.rst | 17 +++++-- 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index a1a46661a..f92702ae0 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -10,52 +10,52 @@ these are particularly good ones to start out with. See our section about the :ref:`community` for a list of resources. -`#bpython` on freenode is particularly useful, but you might have to wait for a while -to get a question answered depending on the time of day. +`#bpython` on freenode is particularly useful, but you might have to wait for a +while to get a question answered depending on the time of day. Getting your development environment set up ------------------------------------------- -bpython supports Python 2.6, 2.7, 3.3 and 3.4. The code is written in Python -2 and transformed for Python 3 with 2to3. This means that it's easier -to work on bpython with Python 2 because you can use ``python setup.py develop`` -or ``pip install -e .`` -(`development mode -`_ installs -by linking to the bpython source instead of copying it over) -in a more straightforward way to have your changes immediately reflected by -your bpython installation and the `bpython`, `bpython-curses`, and `bpython-urwid` -commands. +bpython supports Python 2.6, 2.7, 3.3 and 3.4. The code is compatible with all +supported versions without the need to run post processing like `2to3`. -Using a virtual environment is probably a good idea. Create a virtual environment with +Using a virtual environment is probably a good idea. Create a virtual +environment with .. code-block:: bash - $ virtualenv bpython-dev # determines Python version used - $ source bpython-dev/bin/activate # necessary every time you work on bpython + # determines Python version used + $ virtualenv bpython-dev + # necessary every time you work on bpython + $ source bpython-dev/bin/activate Fork bpython in the GitHub web interface, then clone the repo: .. code-block:: bash $ git clone git@github.com:YOUR_GITHUB_USERNAME/bpython.git - $ # or "git clone https://github.com/YOUR_GITHUB_USERNAME/bpython.git" + # or "git clone https://github.com/YOUR_GITHUB_USERNAME/bpython.git" Next install the install your development copy of bpython and its dependencies: .. code-block:: bash $ cd bpython - $ pip install -e . # install bpython and required dependencies - $ pip install watchdog urwid # install optional dependencies - $ pip install sphinx mock nose # development dependencies + # install bpython and required dependencies + $ pip install -e . + # install optional dependencies + $ pip install watchdog urwid + # development dependencies + $ pip install sphinx mock nose - $ bpython # this runs your modified copy of bpython! + # this runs your modified copy of bpython! + $ bpython .. note:: Many requirements are also available from your distribution's package - manager. On Debian/Ubuntu based systems, the following packages can be used: + manager. On Debian/Ubuntu based systems, the following packages can be + used: .. code-block:: bash @@ -70,8 +70,8 @@ Next install the install your development copy of bpython and its dependencies: .. note:: - Installation of some dependencies with ``pip`` requires Python headers and a - C compiler. These are also available from your package manager. + Installation of some dependencies with ``pip`` requires Python headers and + a C compiler. These are also available from your package manager. .. code-block:: bash @@ -86,8 +86,8 @@ To run tests from the bpython directory: $ nosetests -If you want to skip test cases that are known to be slow, run `nosetests` in the -following way: +If you want to skip test cases that are known to be slow, run `nosetests` in +the following way: .. code-block:: bash @@ -111,9 +111,9 @@ Don't forget to recreate the HTML after you make changes. Hacking on the site or theme ---------------------------- -The site (and its theme as well) is stored in a separate repository and built using -pelican. To start hacking on the site you need to start out with a checkout and -probably a virtual environment: +The site (and its theme as well) is stored in a separate repository and built +using pelican. To start hacking on the site you need to start out with a +checkout and probably a virtual environment: .. code-block:: bash @@ -121,7 +121,7 @@ probably a virtual environment: $ source bpython-site-dev/bin/activate $ pip install pelican -Fork bsite and bsite-theme in the GitHub web interface, then clone the +Fork bsite and bsite-theme in the GitHub web interface, then clone the repositories: .. code-block:: bash @@ -136,11 +136,14 @@ included configuration file. .. code-block:: bash $ source bpython-site-dev/bin/activate - $ cd bsite # if you want to fiddle on the text of the site otherwise go into bsite-theme - $ pelican -t ../bsite-theme -s pelicanconf.py # if you checked out the theme in a different place, use that path - -After this you can open the `output/index.html` in your favourite browser and see -if your changes had an effect. + # if you want to fiddle on the text of the site otherwise go into + # bsite-theme + $ cd bsite + # if you checked out the theme in a different place, use that path + $ pelican -t ../bsite-theme -s pelicanconf.py + +After this you can open the `output/index.html` in your favourite browser and +see if your changes had an effect. .. _GitHub issue tracker: https://github.com/bpython/bpython/issues .. _bite-size: https://github.com/bpython/bpython/labels/bitesize diff --git a/doc/sphinx/source/index.rst b/doc/sphinx/source/index.rst index c551b113d..0c77a79bd 100644 --- a/doc/sphinx/source/index.rst +++ b/doc/sphinx/source/index.rst @@ -1,8 +1,3 @@ -.. bpython documentation master file, created by - sphinx-quickstart on Mon Jun 8 11:58:16 2009. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - bpython documentation ===================== Welcome to the bpython documentation files. This is where you diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst index 52e64696f..299ad1d9d 100644 --- a/doc/sphinx/source/releases.rst +++ b/doc/sphinx/source/releases.rst @@ -16,17 +16,26 @@ After it is decided to release a new version of bpython the following checklist is followed: * The repository is frozen, nobody pushes until the version is built. + * Bob (:ref:`authors`) makes a tarball of the new version and sends it to Simon (:ref:`authors`) who will host it on the bpython website. + * The package is then downloaded by all of the people who like to test it. + * Everybody checks if there are no great problems: - - Version numbers correct? - - CHANGELOG is correct? - - AUTHORS? + + * Version numbers correct? + + * CHANGELOG is correct? + + * AUTHORS? + * After everybody says 'yes' the website and PyPI are updated to point to this new version. - - Simon (:ref:`authors`) also checks if all numbers on the website have been + + * Simon (:ref:`authors`) also checks if all numbers on the website have been updated. + * 24 hours later package maintainers could update their stuff. Checklist From 3e740e8094dd7a6b7df3cc324ca905d6e07a93dc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 9 Feb 2015 18:57:42 +0100 Subject: [PATCH 0416/1650] Use consistent version format Signed-off-by: Sebastian Ramacher --- CHANGELOG | 152 +++++++++++++++++++++++++++--------------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 68275fd4c..b4972b13d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -120,8 +120,8 @@ without twisted installed. * Fix ungetch issues with Python 3.3. See issues #230, #231. -v0.11 ------ +0.11 +---- A bugfix/cleanup release .The fixed bugs are: @@ -133,16 +133,16 @@ frontend, the urwid frontend. I'd like to specifically thank Amjith Ramanujam for his work on history search which was further implemented and is in working order right now. -v0.10.1 -------- +0.10.1 +------ A bugfix release. The fixed bugs are: * #197: find_modules crashes on non-readable directories * #198: Source tarball lacks .po files -v0.10 ------ +0.10 +---- As a highlight of the release, Michele Orrù added i18n support to bpython. Some issues have been resolved as well: @@ -160,16 +160,16 @@ Some issues have been resolved as well: * The short command-line option "-c config" was dropped as it conflicts with vanilla Python's "-c command" option. See issue #186. -v0.9.7.1 --------- +0.9.7.1 +------- A bugfix release. The fixed bugs are: * #128: bpython-gtk is broken * #134: crash when using pastebin and no active internet connection -v0.9.7 ------- +0.9.7 +----- Well guys. It's been some time since the latest release, six months have passed We have added a whole slew of new features, and closed a number of bugs as well. @@ -222,21 +222,21 @@ As always, please submit any bugs you might find to our bugtracker. * #124: Unwanted input when using / keys in the statusbar prompt. -v0.9.6.2 --------- +0.9.6.2 +------- Unfortunately another bugfix release as I (Bob) broke py3 support. * #84: bpython doesn't work with Python 3 Thanks very much to Henry Prêcheur for both the bug report and the patch. -v0.9.6.1 --------- +0.9.6.1 +------- A quick bugfix release (this should not become a habit). * #82: Crash on saving file. -v0.9.6 +0.9.6 ------ A bugfix/feature release (and a start at gtk). Happy Christmas everyone! @@ -260,8 +260,8 @@ A bugfix/feature release (and a start at gtk). Happy Christmas everyone! - Remove globals for configuration. - rl_history now stays the same, also after undo. -v0.9.5.2 --------- +0.9.5.2 +------- A bugfix release. Fixed issues: @@ -275,14 +275,14 @@ Other fixes without opened issues: * future imports in startup scripts can influence interpreter's behaviour now * Show the correct docstring for types without a own __init__ method -v0.9.5.1 +0.9.5.1 -------- Added missing data files to the tarball. -v0.9.5 ------- +0.9.5 +----- Fixed issues: * #25 Problems with DEL, Backspace and C-u over multiple lines @@ -307,8 +307,8 @@ bpaste.net Argument names are now shown as completion suggestions and one can tab through the completion list. -v0.9.4 ------- +0.9.4 +----- Bugfix release (mostly) * when typing a float literal bpython autocompletes int methods (#36) @@ -320,7 +320,7 @@ Bugfix release (mostly) * numerous fixes and improvements to parentheses highlighting * made *all* keys configurable (except for arrow keys/pgup/pgdown) -v0.9.3 +0.9.3 ------ This release was a true whopper! @@ -331,8 +331,8 @@ This release was a true whopper! * Parentheses matching * Argument highlighting -v0.9.2 ------- +0.9.2 +----- * help() now uses an external pager if available. * Fix for highlighting prefixed strings. * Fix to reset string highlighting after a SyntaxError. @@ -340,14 +340,14 @@ v0.9.2 * Configuration files are no longer passed by the first command line argument but by the -c command line switch. * Fix for problem related to editing lines in the history: http://bitbucket.org/bobf/bpython/issue/10/odd-behaviour-when-editing-commands-in-the-history -v0.9.1 ------- +0.9.1 +----- * Fixed a small but annoying bug with sys.argv ini file passing * Fix for Python 2.6 to monkeypatch they way it detects callables in rlcompleter * Config file conversion fix -v0.9.0 ------- +0.9.0 +----- * Module import completion added. * Changed to paste.pocoo.org due to rafb.net no longer offering a pastebin service. * Switched to .ini file format for config file. @@ -357,7 +357,7 @@ v0.9.0 Probably some other things, but I hate changelogs. :) -v0.8.0 +0.8.0 ------ It's been a long while since the last release and there've been numerous little @@ -365,8 +365,8 @@ bugfixes and extras here and there so I'm putting this out as 0.8.0. Check the hg commit history if you want more info: http://bitbucket.org/bobf/bpython/ -v0.7.2 ------- +0.7.2 +----- Menno sent me some patches to fix some stuff: * Socket error handled when submitting to a pastebin. @@ -381,16 +381,16 @@ Other stuff: * Bohdan Vlasyuk sent me a patch that fixes a problem with the above patch from Mark if sys.__stdout__.encoding didn't exist. * Save to file now outputs executable code (i.e. without the >>> and ... and with "# OUT: " prepended to all output lines). I never used this feature much but someone asked for this behaviour. -v0.7.1 ------- +0.7.1 +----- * Added support for a history file, defaults to ~/.pythonhist and 100 lines but is configurable from the rc file (see sample-rc). * Charles Duffy has added a yank/put thing - C-k and C-y. He also ran the code through some PEP-8 checker thing and fixed up a few old habits I manage to break but didn't manage to fix the code to reflect this - thank you! * Jørgen Tjernø has fixed up the autoindentation issues we encountered when bringing soft tabs in. * SyntaxError, ValueError and OverflowError are now caught properly (code.InteractiveInterpreter treats these as different to other exceptions as it doesn't print the whole traceback, so a different handler is called). This was discovered as I was trying to stop autoindentation from occurring on a SyntaxError, which has also been fixed. * '.' now in sys.path on startup. -v0.7.0 ------- +0.7.0 +----- C-d behaviour changed so it no longer exits if the current line isn't empty. Extra linebreak added to end of stdout flush. @@ -406,16 +406,16 @@ raw_input() and all its friends now work fine. PYTHONSTARTUP handled without blowing up on stupid errors (it now parses the file at once instead of feeding it to the repl line-by-line). -v0.6.4 ------- +0.6.4 +----- KeyboardInterrupt handler clears the list window properly now. -v0.6.3 ------- +0.6.3 +----- Forgot to switch rpartition to split for 2.4 compat. -v0.6.2 ------- +0.6.2 +----- The help() now works (as far as I can see) exactly the same as the vanilla help() in the regular interpreter. I copied some code from pydoc.py to make it handle the special cases, e.g. @@ -423,16 +423,16 @@ help('keywords') help('modules') etc. -v0.6.1 ------- +0.6.1 +----- Somehow it escaped my attention that the list window was never fully using the rightmost column, except for the first row. This is because me and numbers don't have the best relationship. I think stability is really improving with the latest spat of bugfixes, keep me informed of any bugs. -v0.6.0 ------- +0.6.0 +----- No noticeable changes except that bpython should now work with Python 2.4. Personally I think it's silly to make a development tool work with an out of date version of Python but some people @@ -440,24 +440,24 @@ seem to disagree. The only real downside is that I had to do a horrible version of all() using reduce(), otherwise there's no real differences in the code. -v0.5.3 ------- +0.5.3 +----- Now you can configure a ~/.bpythonrc file (or pass a rc file at the command line (bpython /foo/bar). See README for details. -v0.5.2 ------- +0.5.2 +----- help() actually displays the full help page, and I fixed up the ghetto pager a little. -v0.5.1 ------- +0.5.1 +----- Now you can hit tab to display the autocomplete list, rather than have it pop up automatically as you type which, apparently, annoys Brendogg. -v0.5.0 ------- +0.5.0 +----- A few people have commented that the help() built-in function doesn't work so well with bpython, since Python will try to output the help string to PAGER (usually "less") which obviously makes @@ -467,8 +467,8 @@ into the interpreter when it initialises in an attempt to rectify this. As such, it's pretty untested but it seems to be working okay for me. Suggestions/bug reports/patches are welcome regarding this. -v0.4.2 ------- +0.4.2 +----- Well, hopefully we're one step closer to making the list sizing stuff work. I really hate doing code for that kind of thing as I never get it quite right, but with perseverence it should end up @@ -481,38 +481,38 @@ experiences. PYTHONSTARTUP is now dealt with and used properly, as per the vanilla interpreter. -v0.4.1 ------- +0.4.1 +----- It looks like the last release was actually pretty bug-free, aside from one tiny bug that NEVER ACTUALLY HAPPENS but someone was bugging me about it anyway, oh well. -v0.4.0 ------- +0.4.0 +----- It's been quite a long time since the last update, due to several uninteresting and invalid excuses, but I finally reworked the list drawing procedures so the crashing seems to have been taken care of to an extent. If it still crashes, the way I've written it will hopefully allow a much more robust way of fixing it, one that might actually work. -v0.3.2 ------- +0.3.2 +----- Thanks to Aaron Gallagher for pointing out a case where the hugely inefficient list generation routines were actually making a significant issue; they're much more efficient now and should hopefully not cause any more problems. -v0.3.1 ------- +0.3.1 +----- Thanks to Klaus Alexander Seis for the expanduser() patch. Auto indent works on multiple levels now. -v0.3.0 ------- +0.3.0 +----- Now with auto-indent. Let me know if it's annoying. -v0.2.4 ------- +0.2.4 +----- Thanks a lot to Angus Gibson for submitting a patch to fix a problem I was having with initialising the keyboard stuff in curses properly. @@ -530,18 +530,18 @@ least that makes sense to me). In so doing I also cleaned up a lot of the reevaluating and resizing code so that a lot of the strange output seen on Rewind/resize seems to be gone. -v0.2.3 ------- +0.2.3 +----- The fix for the last bug broke the positioning of the autocomplete box, whoops. -v0.2.2 ------- +0.2.2 +----- That pesky bug keeps coming up. I think it's finally nailed but it's just a matter of testing and hoping. I hate numbers. -v0.2.1 ------- +0.2.1 +----- I'm having a bit of trouble with some integer division that's causing trouble when a certain set of circumstances arise, and I think I've taken care of that little bug, since it's @@ -549,8 +549,8 @@ a real pain in the ass and only creeps up when I'm actually doing something useful, so I'll test it for a bit and release it as hopefully a bug fixed version. -v0.2.0 ------- +0.2.0 +----- A little late in the day to start a changelog, but here goes... This version fixed another annoying little bug that was causing crashes given certain exact circumstances. I always find it's the From 98dfdbb7682e739a2a7733cae665b981d80c1b41 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 9 Feb 2015 18:58:04 +0100 Subject: [PATCH 0417/1650] Replace changelog.rst with symlink to CHANGELOG Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/changelog.rst | 563 +------------------------------- 1 file changed, 1 insertion(+), 562 deletions(-) mode change 100644 => 120000 doc/sphinx/source/changelog.rst diff --git a/doc/sphinx/source/changelog.rst b/doc/sphinx/source/changelog.rst deleted file mode 100644 index da8b74e75..000000000 --- a/doc/sphinx/source/changelog.rst +++ /dev/null @@ -1,562 +0,0 @@ -Changelog -========= - -0.13.2 ------- - -A bugfix release. The fixed bugs are: - -* #424: Use new JSON API at bpaste.net. -* #430: Fixed SNI issues with new pastebin service on Mac OS X. -* #432: Fixed crash in bpython-curtsies in special circumstances if history file - is empty. Thanks to Lisa van Gelder. - -0.13.1 ------- - -A bugfix release. The fixed bugs are: - -* #287: Turned off dictionary completion in bpython-curtsies -* #281: Fixed a crash on error-raising properties -* #286: Fixed input in Python 3 -* #293: Added encoding attribute to stdin bpython curtsies -* #296: Fixed warnings in import completion for Python 3 -* #290: Stop using root logger -* #301: Specify curtsies version in requirements - -There's also a necessary regression: #232 (adding fileno() on stdin) -is reintroduced because its previous fix was found to be the cause of #286 - -0.13 ----- - -There are a few new features, a bunch of bugfixes, and a new frontend -for bpython in this release. - -* Dictionary key completion, thanks to Maja Frydrychowicz (#226). - To use normal completion and ignore these key completions, type a space. -* Edit current line in external editor: ctrl-x (#161) - -Fixes: - -* Python 2.5 compatibility, thanks to Michael Schuller (#279). Python 2.5 - is not officially supported, but after few changes Michael introduced, he - says it's working fine. -* FakeStream has flush(), so works correctly with - django.core.email.backends.console thanks to Marc Sibson (#259) -* FakeStdin has fileno() (#232) -* Changes to sys.ps1 and sys.ps2 are respected thanks to Michael Schulle (#267) -* atexit registered functions run on exit (#258) -* fixed an error on exit code when running a script with bpython script.py (#260) -* setup.py extras are used to define dependencies for urwid and - curtsies frontends - -There's a new frontend for bpython: bpython-curtsies. Curtsies is a terminal -wrapper written to making native scrolling work in bpython. (#56, #245) -Try bpython-curtsies for the bpython experience with a vanilla python -layout. (demo: -http://ballingt.com/assets/bpython-curtsies-scroll-demo-large.gif) - -This curtsies frontend addresses some issues unfixed in bpython-cli, and has -a few extra features: - -* Editing full interpreter history in external editor with F7, which is rerun - as in rewind -* A new interpreter is used for rewind, unless bpython-curtsies was started - with custom locals or in interactive mode (#71) -* Ctrl-c behaves more like vanilla python (#177) -* Completion still works if cursor at the end of the line (#147) -* Movement keys meta-b, meta-f, and meta-backspace, ctrl-left and ctrl-right - are all honored (#246, #201) -* Non-ascii characters work in the file save prompt (#236) -* New --type / -t option to run the contents of a file as though they were - typed into the bpython-curtsies prompt - -A few things about bpython-curtsies are worse than regular bpython: - -* Bad things can happen when using several threads (#265) -* output prints slowly (#262) -* bpython-curtsies can't be backgrounded and resumed correctly (via ctrl-z, - fg) (#274) - -There are two new options in the new [curtsies] section of the bpython config - -* list_above: whether completion window can cover text above the current line; - defaults to True -* fill_terminal: whether bpython-curtsies should be fullscreen (like bpython); - defaults to False - -0.12 ----- - -We want to give special thanks to the Hacker School project- -(https://www.hackerschool.com/) for choosing bpython as their pet hacking -project. In special we would like to thank the following people for contributing -their code to bpython: - -- Martha Girdler -- Allison Kaptur -- Ingrid Cheung - -We'd also like to thank Eike Hein for contributing his pastebin code which now -makes it possible to paste using a 3rd party program unlocking a whole slew of -pastebins for bpython users. - -* Added a new pastebin_helper config option to name an executable that should - perform pastebin upload on bpython's behalf. If set, this overrides - pastebin_url. Data is supplied to the helper via STDIN, and it is expected - to return a pastebin URL as the first word of its output. -* Fixed a bug causing pastebin upload to fail after a previous attempt was - unsuccessful. A duplicate pastebin error would be displayed in this case, - despite the original upload having failed. -* Added more key shortcuts to bpython.urwid -* Smarter dedenting after certain expressions -* #74 fixed broken completion when auto_display_list was disabled - -We also have done numerous cleanup actions including building the man pages from -our documentation. Including the documentation in the source directory. Some -minor changes to the README to have EOL 79 and changes to urwid to work better -without twisted installed. - -* Fix ungetch issues with Python 3.3. See issues #230, #231. - -v0.11 ------ - -A bugfix/cleanup release .The fixed bugs are: - -* #204: "import math" not autocompleting on python 3.2 - -Otherwise lots of small additions to the to be replacement for our ncurses -frontend, the urwid frontend. - -I'd like to specifically thank Amjith Ramanujam for his work on history search -which was further implemented and is in working order right now. - -v0.10.1 -------- - -A bugfix release. The fixed bugs are: - -* #197: find_modules crashes on non-readable directories -* #198: Source tarball lacks .po files - -v0.10 ------ -As a highlight of the release, Michele Orrù added i18n support to bpython. - -Some issues have been resolved as well: - -* Config files are now located according to the XDG Base Directory - Specification. The support for the old bpythonrc files has been - dropped and ~/.bpython.ini as config file location is no longer supported. - See issue #91. -* Fixed some issues with tuple unpacking in argspec. See issues #133 and #138. -* Fixed a crash with non-ascii filenames in import completion. See issue #139. -* Fixed a crash caused by inspect.findsource() raising an IndexError - which happens in some situations. See issue #94. -* Non-ascii input should work now under Python 3. -* Issue #165: C-a and C-e do the right thing now in urwid. -* The short command-line option "-c config" was dropped as it conflicts with - vanilla Python's "-c command" option. See issue #186. - -v0.9.7.1 --------- - -A bugfix release. The fixed bugs are: - -* #128: bpython-gtk is broken -* #134: crash when using pastebin and no active internet connection - -v0.9.7 ------- - -Well guys. It's been some time since the latest release, six months have passed -We have added a whole slew of new features, and closed a number of bugs as well. - -We also have a new frontend for bpython. Marien Zwart contributed a urwid -frontend as an alternative for the curses frontend. Be aware that there still -is a lot to fix for this urwid frontend (a lot of the keyboard shortcuts do not -yet work for example) but please give it a good spin. Urwid also optionally -integrates with a Twisted reactor and through that with things like the GTK -event loop. - -At the same time we have done a lot of work on the GTK frontend. The GTK -frontend is now 'usable'. Please give that a spin as well by running bpython-gtk -on you system. - -We also welcome a new contributor in the name of Michele Orrù who we hope will -help us fix even more bugs and improve functionality. - -As always, please submit any bugs you might find to our bugtracker. - -* Pastebin confirmation added; we were getting a lot of people accidentally - pastebinning sensitive information so I think this is a good idea. -* Don't read PYTHONSTARTUP when executed with -i. -* BPDB was merged in. BPDB is an extension to PDB which allows you to press B - in a PDB session which will let you be dropped into a bpython sessions with - the current PDB locals(). For usage, see the documentation. -* The clear word shortcut (default: C-w) now deletes to the buffer. -* More tests have been added to bpython. -* The pastebin now checks for a previous paste (during the session) with the - exact same content to guard against twitchy fingers pastebinning multiple - times. -* Let import completion return "import " instead of "import". - -* GTK now has pastebin, both for full log as well as the current selection. -* GTK now has write2file. -* GTK now has a menu. -* GTK now has a statusbar. -* GTK now has show source functionality. -* GTK saves the pastebin url to the clipboard. -* GTK now has it's own configuration section. -* Set focus to the GTK text widget to allow for easier embedding in PIDA and - others which fixes issues #121. - -* #87: Add a closed attribute to Repl to fix mercurial.ui.ui expecting stderr - to have this attribute. -* #108: Unicode characters in docsrting crash bpython -* #118: Load_theme is not defined. -* #99: Configurable font now documented. -* #123: Pastebin can't handle 'ESC' key -* #124: Unwanted input when using / keys in the statusbar prompt. - - -v0.9.6.2 --------- -Unfortunately another bugfix release as I (Bob) broke py3 support. - -* #84: bpython doesn't work with Python 3 - Thanks very much to Henry Prêcheur for both the bug report and the - patch. - -v0.9.6.1 --------- -A quick bugfix release (this should not become a habit). - -* #82: Crash on saving file. - -v0.9.6 ------- -A bugfix/feature release (and a start at gtk). Happy Christmas everyone! - -* #67: Make pastebin URL really configurable. -* #68: Set a__main__ module and set interpreter's namespace to that module. -* #70: Implement backward completion on backward tab. -* #62: Hide matches starting with a _ unless explicitly typed. -* #72: Auto dedentation -* #78: Theme without a certain value raises exception - -- add the possibility for a banner to be shown on bpython startup (when - embedded or in code) written by Caio Romao. -- add a hack to add a write() method to our fake stdin object -- Don't use curses interface when stdout is not attached to a terminal. -- PEP-8 conformance. -- Only restore indentation when inside a block. -- Do not decrease the lineno in tracebacks for Py3 -- Do not add internal code to history. -- Make paren highlighting more accurate. -- Catch SyntaxError in import completion. -- Remove globals for configuration. -- rl_history now stays the same, also after undo. - -v0.9.5.2 --------- - -A bugfix release. Fixed issues: - -* #60: Filename expansion: Cycling completions and deleting -* #61: Filename expansion: Directory names with '.'s get mangled - -Other fixes without opened issues: - -* Encode items in the suggestion list properly -* Expand usernames in file completion correctly -* future imports in startup scripts can influence interpreter's behaviour now -* Show the correct docstring for types without a own __init__ method - -v0.9.5.1 --------- - -Added missing data files to the tarball. - - -v0.9.5 ------- -Fixed issues: - -* #25 Problems with DEL, Backspace and C-u over multiple lines -* #49 Sending last output to $PAGER -* #51 Ability to embed bpython shell into an existing script -* #52 FakeStdin.readlines() is broken -* #53 Error on printing null character -* #54 Parsing/introspection ncurses viewer neglects parenthesis - -bpython has added a view source shortcut to show the source of the current -function. - -The history file is now really configurable. This issue was reported -in Debian's bugtracker. - -bpython has now some basic support for Python 3 (requires Pygments >=1.1.1). -As a result, setuptools is now optional. - -The pastebin URL is now configurable and the default pastebin is now -bpaste.net - -Argument names are now shown as completion suggestions and one can -tab through the completion list. - -v0.9.4 ------- -Bugfix release (mostly) - -* when typing a float literal bpython autocompletes int methods (#36) -* Autocompletion for file names (#40) -* Indenting doesn't reset (#27) -* bpython configuration has moved from ~/.bpython.ini to ~/.bpython/config (currently still supporting fallback) -* leftovers of statusbar when exiting bpython cleaned up -* bpython now does not crash when a 'popup' goes out of window bounds -* numerous fixes and improvements to parentheses highlighting -* made *all* keys configurable (except for arrow keys/pgup/pgdown) - -v0.9.3 ------- -This release was a true whopper! - -* Full unicode support -* Configurable hotkey support -* Theming support -* Pastemode, disables syntax highlighting during a paste for faster pasting, highlights when done -* Parentheses matching -* Argument highlighting - -v0.9.2 ------- -* help() now uses an external pager if available. -* Fix for highlighting prefixed strings. -* Fix to reset string highlighting after a SyntaxError. -* bpython now uses optparse for option parsing and it supports --version now. -* Configuration files are no longer passed by the first command line argument but by the -c command line switch. -* Fix for problem related to editing lines in the history (#10) - -v0.9.1 ------- -* Fixed a small but annoying bug with sys.argv ini file passing -* Fix for Python 2.6 to monkeypatch they way it detects callables in rlcompleter -* Config file conversion fix - -v0.9.0 ------- -* Module import completion added. -* Changed to paste.pocoo.org due to rafb.net no longer offering a pastebin service. -* Switched to .ini file format for config file. -* White background-friendly colour scheme added. -* C-l now clears the screen. -* SyntaxError now correctly added to history to prevent it garbling up on a redraw. - -Probably some other things, but I hate changelogs. :) - -v0.8.0 ------- - -It's been a long while since the last release and there've been numerous little -bugfixes and extras here and there so I'm putting this out as 0.8.0. Check the -hg commit history if you want more info: -http://bitbucket.org/bobf/bpython/ - -v0.7.2 ------- -Menno sent me some patches to fix some stuff: - -* Socket error handled when submitting to a pastebin. -* Resizing could crash if you resize small enough. - -Other stuff: - -* 'self' in arg list is now highlighted a different colour. -* flush_output option added to config to control whether output is flushed to stdout or not on exit. -* Piping something to bpython made it lock up as stdin was not the keyboard - bpython just executes stdin and exits instead of trying to do something clever. -* Mark Florisson (eggy) gave me a patch that stops weird breakage when unicode objects get added into the output buffer - they now get encoded into the output encoding. -* Bohdan Vlasyuk sent me a patch that fixes a problem with the above patch from Mark if sys.__stdout__.encoding didn't exist. -* Save to file now outputs executable code (i.e. without the >>> and ... and with "# OUT: " prepended to all output lines). I never used this feature much but someone asked for this behaviour. - -v0.7.1 ------- -* Added support for a history file, defaults to ~/.pythonhist and 100 lines but is configurable from the rc file (see sample-rc). -* Charles Duffy has added a yank/put thing - C-k and C-y. He also ran the code through some PEP-8 checker thing and fixed up a few old habits I manage to break but didn't manage to fix the code to reflect this - thank you! -* Jørgen Tjernø has fixed up the autoindentation issues we encountered when bringing soft tabs in. -* SyntaxError, ValueError and OverflowError are now caught properly (code.InteractiveInterpreter treats these as different to other exceptions as it doesn't print the whole traceback, so a different handler is called). This was discovered as I was trying to stop autoindentation from occurring on a SyntaxError, which has also been fixed. -* '.' now in sys.path on startup. - -v0.7.0 ------- -C-d behaviour changed so it no longer exits if the current line isn't empty. - -Extra linebreak added to end of stdout flush. - -pygments and pyparsing are now dependencies. - -Jørgen Tjernø has done lots of cool things like write a manpage and .desktop -file and improved the way tabbing works and also added home, end and del key -handling as well as C-w for deleting words - thanks a lot! - -raw_input() and all its friends now work fine. - -PYTHONSTARTUP handled without blowing up on stupid errors (it now parses the -file at once instead of feeding it to the repl line-by-line). - -v0.6.4 ------- -KeyboardInterrupt handler clears the list window properly now. - -v0.6.3 ------- -Forgot to switch rpartition to split for 2.4 compat. - -v0.6.2 ------- -The help() now works (as far as I can see) exactly the same -as the vanilla help() in the regular interpreter. I copied some -code from pydoc.py to make it handle the special cases, e.g. -help('keywords') -help('modules') -etc. - -v0.6.1 ------- -Somehow it escaped my attention that the list window was never -fully using the rightmost column, except for the first row. This -is because me and numbers don't have the best relationship. I think -stability is really improving with the latest spat of bugfixes, -keep me informed of any bugs. - -v0.6.0 ------- -No noticeable changes except that bpython should now work with -Python 2.4. Personally I think it's silly to make a development -tool work with an out of date version of Python but some people -seem to disagree. The only real downside is that I had to do a -horrible version of all() using reduce(), otherwise there's no -real differences in the code. - -v0.5.3 ------- -Now you can configure a ~/.bpythonrc file (or pass a rc file at the -command line (bpython /foo/bar). See README for details. - -v0.5.2 ------- -help() actually displays the full help page, and I fixed up the -ghetto pager a little. - -v0.5.1 ------- -Now you can hit tab to display the autocomplete list, rather than -have it pop up automatically as you type which, apparently, annoys -Brendogg. - -v0.5.0 ------- -A few people have commented that the help() built-in function -doesn't work so well with bpython, since Python will try to output -the help string to PAGER (usually "less") which obviously makes -everything go wrong when curses is involved. With a bit of hackery -I've written my own ghetto pager and injected my own help function -into the interpreter when it initialises in an attempt to rectify this. -As such, it's pretty untested but it seems to be working okay for me. -Suggestions/bug reports/patches are welcome regarding this. - -v0.4.2 ------- -Well, hopefully we're one step closer to making the list sizing -stuff work. I really hate doing code for that kind of thing as I -never get it quite right, but with perseverence it should end up -being completely stable; it's not the hardest thing in the world. - -Various cosmetic fixes have been put in at the request of a bunch -of people who were kind enough to send me emails regarding their -experiences. - -PYTHONSTARTUP is now dealt with and used properly, as per the vanilla -interpreter. - -v0.4.1 ------- -It looks like the last release was actually pretty bug-free, aside -from one tiny bug that NEVER ACTUALLY HAPPENS but someone was bugging -me about it anyway, oh well. - -v0.4.0 ------- -It's been quite a long time since the last update, due to several -uninteresting and invalid excuses, but I finally reworked the list -drawing procedures so the crashing seems to have been taken care of -to an extent. If it still crashes, the way I've written it will hopefully -allow a much more robust way of fixing it, one that might actually work. - -v0.3.2 ------- -Thanks to Aaron Gallagher for pointing out a case where the hugely -inefficient list generation routines were actually making a significant -issue; they're much more efficient now and should hopefully not cause -any more problems. - -v0.3.1 ------- -Thanks to Klaus Alexander Seis for the expanduser() patch. -Auto indent works on multiple levels now. - -v0.3.0 ------- -Now with auto-indent. Let me know if it's annoying. - -v0.2.4 ------- -Thanks a lot to Angus Gibson for submitting a patch to fix a problem -I was having with initialising the keyboard stuff in curses properly. - -Also a big thanks to John Beisley for providing the patch that shows -a class __init__ method's argspec on class instantiation. - -I've fixed up the argspec display so it handles really long argspecs -(e.g. subprocess.Popen()) and doesn't crash if something horrible -happens (rather, it avoids letting something horrible happen). - -I decided to add a key that will get rid of the autocomplete window, -since it can get in the way. C-l seemed like a good choice, since -it would work well as a side-effect of redrawing the screen (at -least that makes sense to me). In so doing I also cleaned up a lot -of the reevaluating and resizing code so that a lot of the strange -output seen on Rewind/resize seems to be gone. - -v0.2.3 ------- -The fix for the last bug broke the positioning of the autocomplete -box, whoops. - -v0.2.2 ------- -That pesky bug keeps coming up. I think it's finally nailed but -it's just a matter of testing and hoping. I hate numbers. - -v0.2.1 ------- -I'm having a bit of trouble with some integer division that's -causing trouble when a certain set of circumstances arise, -and I think I've taken care of that little bug, since it's -a real pain in the ass and only creeps up when I'm actually -doing something useful, so I'll test it for a bit and release -it as hopefully a bug fixed version. - -v0.2.0 ------- -A little late in the day to start a changelog, but here goes... -This version fixed another annoying little bug that was causing -crashes given certain exact circumstances. I always find it's the -way with curses and sizing of windows and things... - -I've also got bpython to try looking into pydoc if no matches -are found for the argspec, which means the builtins have argspecs -too now, hooray. - diff --git a/doc/sphinx/source/changelog.rst b/doc/sphinx/source/changelog.rst new file mode 120000 index 000000000..b6b15a7d0 --- /dev/null +++ b/doc/sphinx/source/changelog.rst @@ -0,0 +1 @@ +../../../CHANGELOG \ No newline at end of file From 2d4fc0b511513c35858f58ecd4bcd6f27bfbd376 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 9 Feb 2015 20:35:25 +0100 Subject: [PATCH 0418/1650] Update copyright information Signed-off-by: Sebastian Ramacher --- bpython/_py3compat.py | 2 +- bpython/clipboard.py | 2 +- bpython/config.py | 2 +- bpython/filelock.py | 4 +++- bpython/history.py | 5 ++++- bpython/inspection.py | 5 ++++- bpython/lazyre.py | 2 +- bpython/repl.py | 3 +++ 8 files changed, 18 insertions(+), 7 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index 91069e97e..b6d4b5389 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -3,6 +3,7 @@ # The MIT License # # Copyright (c) 2012 the bpython authors. +# Copyright (c) 2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -21,7 +22,6 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# """ diff --git a/bpython/clipboard.py b/bpython/clipboard.py index 200fbfa93..a37572a56 100644 --- a/bpython/clipboard.py +++ b/bpython/clipboard.py @@ -1,6 +1,6 @@ # The MIT License # -# Copyright (c) 2015 the bpython authors. +# Copyright (c) 2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/bpython/config.py b/bpython/config.py index 9cf454374..711996568 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# encoding: utf-8 from __future__ import with_statement import os diff --git a/bpython/filelock.py b/bpython/filelock.py index 532cb4b7c..94cb16d3e 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -1,6 +1,8 @@ +# encoding: utf-8 + # The MIT License # -# Copyright (c) 2015 the bpython authors. +# Copyright (c) 2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/bpython/history.py b/bpython/history.py index a12a1e3b1..7aaa8acfe 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -1,6 +1,9 @@ +# encoding: utf-8 + # The MIT License # -# Copyright (c) 2009-2015 the bpython authors. +# Copyright (c) 2009 the bpython authors. +# Copyirhgt (c) 2012,2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/bpython/inspection.py b/bpython/inspection.py index cdcea8949..77fa6fbf6 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -1,6 +1,9 @@ +# encoding: utf-8 + # The MIT License # -# Copyright (c) 2009-2015 the bpython authors. +# Copyright (c) 2009-2011 the bpython authors. +# Copyright (c) 2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 62792e312..f2b29c574 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -1,6 +1,6 @@ # The MIT License # -# Copyright (c) 2015 the bpython authors. +# Copyright (c) 2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/bpython/repl.py b/bpython/repl.py index 54a0c78c1..43278fe0d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1,6 +1,9 @@ +# encoding: utf-8 + # The MIT License # # Copyright (c) 2009-2011 the bpython authors. +# Copyright (c) 2012-2013,2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal From 6547d6de9f7983fa3fcad00afc8ebfab642e0c96 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 9 Feb 2015 20:35:43 +0100 Subject: [PATCH 0419/1650] Handle encodings properly in super paste mode Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 7a2b4246d..5c8aaac32 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -3,6 +3,7 @@ import code import logging import sys +import io from optparse import Option import curtsies @@ -17,6 +18,7 @@ from bpython.translations import _ from bpython.importcompletion import find_iterator from bpython.curtsiesfrontend import events as bpythonevents +from bpython import inspection logger = logging.getLogger(__name__) @@ -57,7 +59,9 @@ def main(args=None, locals_=None, banner=None): exit_value = 0 if options.type: paste = curtsies.events.PasteEvent() - sourcecode = open(exec_args[0]).read() + encoding = inspection.get_encoding_file(exec_args[0]) + with io.open(exec_args[0], encoding=encoding) as f: + sourcecode = f.read() paste.events.extend(sourcecode) else: try: From a82e64804711c609ab49c7e0f627486f183a5e2e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 9 Feb 2015 21:20:32 +0100 Subject: [PATCH 0420/1650] Start working on 0.14 changelog --- CHANGELOG | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index b4972b13d..cc61e36b7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,29 @@ Changelog ========= +0.14 +---- + +This release contains major changes to the frontends: + +* curtsies is the new default frontend. +* The old curses frontend is available as bpython-cli. +* The GTK+ frontend has been removed. + +New features: + +* French translation. +* #234: Copy to clipboard. + +Fixes: + +* #284: __file__ is now defined. +* #366, #367: Fixed help support in curtsies. +* #432: Fix crash if no history is present. + +Python 2.6, 2.7, 3.3 and newer are supported. Support for 2.5 has been dropped. +Furthermore, it is no longer necessary to run 2to3 on the source code. + 0.13.2 ------- From 844d350810f3e11fa4d02045fec8f50d6072a323 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Feb 2015 02:57:07 +0100 Subject: [PATCH 0421/1650] Fix off-by-one Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 5c8aaac32..f3c25cdae 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -41,7 +41,7 @@ def main(args=None, locals_=None, banner=None): if options.log is None: options.log = 0 logging_levels = [logging.ERROR, logging.INFO, logging.DEBUG] - level = logging_levels[min(len(logging_levels), options.log)] + level = logging_levels[min(len(logging_levels) - 1, options.log)] logging.getLogger('curtsies').setLevel(level) logging.getLogger('bpython').setLevel(level) if options.log: From 62d7d9551aa0edfe6e68855d6788ef86538361aa Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 00:08:28 +0100 Subject: [PATCH 0422/1650] Use io instead of codecs Signed-off-by: Sebastian Ramacher --- bpython/history.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bpython/history.py b/bpython/history.py index 7aaa8acfe..c915d2dce 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -24,7 +24,7 @@ # THE SOFTWARE. -import codecs +import io import os from itertools import islice from six.moves import range @@ -175,7 +175,8 @@ def reset(self): self.saved_line = '' def load(self, filename, encoding): - with codecs.open(filename, 'r', encoding, 'ignore') as hfile: + with io.open(filename, 'r', encoding=encoding, + errors='ignore') as hfile: with FileLock(hfile): self.entries = self.load_from(hfile) @@ -186,7 +187,8 @@ def load_from(self, fd): return entries def save(self, filename, encoding, lines=0): - with codecs.open(filename, 'w', encoding, 'ignore') as hfile: + with io.open(filename, 'w', encoding=encoding, + errors='ignore') as hfile: with FileLock(hfile): self.save_to(hfile, self.entries, lines) @@ -202,7 +204,8 @@ def append_reload_and_write(self, s, filename, encoding): return self.append(s) try: - with codecs.open(filename, 'a+', encoding, 'ignore') as hfile: + with io.open(filename, 'a+', encoding=encoding, + errors='ignore') as hfile: with FileLock(hfile): # read entries hfile.seek(0, os.SEEK_SET) From 26d2065751e542e1c49caa562e8ee97b22e16e73 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 01:04:19 +0100 Subject: [PATCH 0423/1650] Add missing unicode_literals import Signed-off-by: Sebastian Ramacher --- bpython/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/history.py b/bpython/history.py index c915d2dce..d852897c8 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -23,7 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - +from __future__ import unicode_literals import io import os from itertools import islice From 09d881e8a3e29828907c4d1705093406c4cc39ac Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 13:31:48 +0100 Subject: [PATCH 0424/1650] Add bpdp to console_scripts Signed-off-by: Sebastian Ramacher --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index faed25f1d..bdbedd372 100755 --- a/setup.py +++ b/setup.py @@ -193,7 +193,8 @@ def initialize_options(self): 'console_scripts': [ 'bpython = bpython.curtsies:main', 'bpython-curses = bpython.cli:main', - 'bpython-urwid = bpython.urwid:main [urwid]' + 'bpython-urwid = bpython.urwid:main [urwid]', + 'bpbd = bpdb:main' ] } From cc3d8c8e651aec357fb8e9a7b3cd7654f45f63af Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 15:17:53 +0100 Subject: [PATCH 0425/1650] Add bpython.__main__ Signed-off-by: Sebastian Ramacher --- bpython/__main__.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 bpython/__main__.py diff --git a/bpython/__main__.py b/bpython/__main__.py new file mode 100644 index 000000000..a452a6364 --- /dev/null +++ b/bpython/__main__.py @@ -0,0 +1,29 @@ +# encoding: utf-8 + +# The MIT License +# +# Copyirhgt (c) 2015 Sebastian Ramacher +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import sys + +if __name__ == '__main__': + from bpython.curtsies import main + sys.exit(main()) From fac76b005c22426611c70f85f9e3a60b4f6c53b3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 15:37:08 +0100 Subject: [PATCH 0426/1650] Fix changelog entry Signed-off-by: Sebastian Ramacher --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index cc61e36b7..8da4956d2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,7 +7,7 @@ Changelog This release contains major changes to the frontends: * curtsies is the new default frontend. -* The old curses frontend is available as bpython-cli. +* The old curses frontend is available as bpython-curses. * The GTK+ frontend has been removed. New features: From fec214624b9f9f3a0c346b7e474dbc874357725e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 10 Feb 2015 14:40:42 -0500 Subject: [PATCH 0427/1650] update changelog with last ~500 commits --- CHANGELOG | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8da4956d2..d7801b8f5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,24 +6,101 @@ Changelog This release contains major changes to the frontends: -* curtsies is the new default frontend. -* The old curses frontend is available as bpython-curses. -* The GTK+ frontend has been removed. +* curtsies is the new default frontend +* the old curses frontend is available as bpython-curses +* the GTK+ frontend has been removed New features: * French translation. -* #234: Copy to clipboard. +* #338: bpython help with F1 +* #354: edit config file from within bpython with F3 +* #322: watch imported modules for changes and reevaluate on changes + which requires optional dependency "watchdog" Toggle with F5 +* #285: reevaluate session and reimport modules with F6 +* #234 copy to system keyboard with F10 +* 2 new fish shell style features enabled by default, disable by setting + right_arrow_completion in the [curtsies] section of the config to False + * right arrow completion: the last readline history + line which the current line could complete to appears in gray and can + be completed by hitting the right arrow key, enabled in config with + right_arrow_completion thanks to Nicholas Sweeting + * fish shell style automatic reverse history search: up arrow searches + readline history for entries containing the contents of the current line + thanks to Nicholas Sweeting +* incremental forward and reverse search, be default bound to meta-r and + meta-s because the standard keys ctrl-r and ctrl-s have well established + uses in bpython. These can be changed in the config for a more readline- + faithful bpython experience. +* #410: startup banner that shows Python and bpython version +* #334: readline command meta-. for yank last argument works + thanks to Susan Steinman and Steph Samson +* all readline keys which kill/cut text correctly copy text for paste + with ctrl-y or meta-y +* #194: Syntax-highlighted tracebacks thanks to Miriam Lauter +* #328: bpython history not reevaluated to edit a previous line of a + multiline statement +* removal links provided for bpaste pastebins +* more informative error messages when source can't be found for an object + thanks to Liudmila Nikolaeva and Miriam +* Message displayed if history in scrollback buffer is inconsistent with + output from last reevaluation of bpython session thanks to Susan Steinman +* Adjust logging level with -L or -LL +* #426 mutliline autocompletion (requires optional dependency Jedi) +* #313 warn when undo may take cause extended delay, and prompt to undo + multiple lines (set threshold in config with single_undo_time) +* #382 partial support for pasting in text with blank lines +* basic string literal completion Fixes: -* #284: __file__ is now defined. * #366, #367: Fixed help support in curtsies. * #432: Fix crash if no history is present. +* #347: Fix crash on unsafe autocompletion +* #349: writing newlines to stderr +* #363: banner no longer crashes bpython-urwid - thanks to Luca Barbato +* #369: interactive sessions inherit compiler directives from files + run with -i interactive flag +* #341: right arrow key suggestion color is configurable thanks to + Alice Chen +* #391: fix crash when using meta-backspace thanks to Tony Wang +* doc fixes from Lindsey Raymond, +* #284: __file__ is in scope after module run with bpython -i + thanks to Lindsey +* #254: replacement box characters used for ascii terminals +* #442: add message for turning filewatching off thanks to Keyan Pishdadian +* #435: filewatching api improved and tested thanks to Keyan Pishdadian +* #447: fix behavior of duplicate keybindings +* #458: fix dictionary key completion crash in Python 2.6 thanks to + Mary Mokuolu +* #438, #450: bpython-curtsies startup behavior fixed - runs on startup, + errors during startup reported instead of crashing + +There was a lot of assorted work bringing the curtsies frontend +up to the standards of bpython-curses: + +* match list dissapears correctly when single match is selected +* Filename completion working correctly +* dictionary key completion fixed and re-enabled +* pager for showing source and native help() viewing +* better status bar message display and clear code +* unicode issues +* curtsies properly suspends and resumes (ctrl-z, fg) +* Assorted curtsies display issues fixed (#472, #468, #448, #440, #401, +#370) Python 2.6, 2.7, 3.3 and newer are supported. Support for 2.5 has been dropped. Furthermore, it is no longer necessary to run 2to3 on the source code. +This release brings a lot more code coverage, a new contributing guide, +and so much more pep8 + +new dependencies - the optional ones are for urwid, watching files, and +completion +new dependencies for HTTPS on OSX with Python 2 + +nicer version numbers + 0.13.2 ------- From 642ecb8c3e481be8ada531929f4217c6afe5949d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 21:16:59 +0100 Subject: [PATCH 0428/1650] Add dependency changes to 0.13.2 --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index d7801b8f5..02e39039c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -111,6 +111,11 @@ A bugfix release. The fixed bugs are: * #432: Fixed crash in bpython-curtsies in special circumstances if history file is empty. Thanks to Lisa van Gelder. +Changes to dependencies: + +* requests is a new dependency. +* PyOpenSSL, ndg-httpsclient and pyasn1 are new dependencies on Mac OS X. + 0.13.1 ------- From 5b21b3c18fe2e8e59e25305b0136bb3fbf51d99e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 21:17:55 +0100 Subject: [PATCH 0429/1650] Extra list for dependency changes Signed-off-by: Sebastian Ramacher --- CHANGELOG | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 02e39039c..c3c475dc4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -95,11 +95,11 @@ Furthermore, it is no longer necessary to run 2to3 on the source code. This release brings a lot more code coverage, a new contributing guide, and so much more pep8 -new dependencies - the optional ones are for urwid, watching files, and -completion -new dependencies for HTTPS on OSX with Python 2 +Changes to dependencies: -nicer version numbers +* greenlet and curtsies are no longer optional. +* six is new dependency. +* jedi and watchdog are new optional dependencies. 0.13.2 ------- From d9f5b9fbe79098d97205097ee24c269ae15034cd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 21:18:19 +0100 Subject: [PATCH 0430/1650] Sort fixes by bug number and do not include fixes specific to new features Signed-off-by: Sebastian Ramacher --- CHANGELOG | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c3c475dc4..81e6c2240 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -54,27 +54,21 @@ New features: Fixes: -* #366, #367: Fixed help support in curtsies. -* #432: Fix crash if no history is present. +* #254: replacement box characters used for ascii terminals +* #284: __file__ is in scope after module run with bpython -i thanks to Lindsey +* #341: right arrow key suggestion color is configurable thanks to Alice Chen * #347: Fix crash on unsafe autocompletion * #349: writing newlines to stderr * #363: banner no longer crashes bpython-urwid - thanks to Luca Barbato -* #369: interactive sessions inherit compiler directives from files - run with -i interactive flag -* #341: right arrow key suggestion color is configurable thanks to - Alice Chen +* #366, #367: Fixed help() support in curtsies. +* #369: interactive sessions inherit compiler directives from files run with -i + interactive flag * #391: fix crash when using meta-backspace thanks to Tony Wang -* doc fixes from Lindsey Raymond, -* #284: __file__ is in scope after module run with bpython -i - thanks to Lindsey -* #254: replacement box characters used for ascii terminals -* #442: add message for turning filewatching off thanks to Keyan Pishdadian -* #435: filewatching api improved and tested thanks to Keyan Pishdadian +* #438, #450: bpython-curtsies startup behavior fixed - runs on startup, errors + during startup reported instead of crashing * #447: fix behavior of duplicate keybindings -* #458: fix dictionary key completion crash in Python 2.6 thanks to - Mary Mokuolu -* #438, #450: bpython-curtsies startup behavior fixed - runs on startup, - errors during startup reported instead of crashing +* #458: fix dictionary key completion crash in Python 2.6 thanks to Mary Mokuolu +* documenatation fixes from Lindsey Raymond There was a lot of assorted work bringing the curtsies frontend up to the standards of bpython-curses: From f1145c3672826a5276d6e88ef4ca33f210cc05e6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 21:18:37 +0100 Subject: [PATCH 0431/1650] Formatting Signed-off-by: Sebastian Ramacher --- CHANGELOG | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 81e6c2240..d83aa43de 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,16 +18,14 @@ New features: * #322: watch imported modules for changes and reevaluate on changes which requires optional dependency "watchdog" Toggle with F5 * #285: reevaluate session and reimport modules with F6 -* #234 copy to system keyboard with F10 -* 2 new fish shell style features enabled by default, disable by setting - right_arrow_completion in the [curtsies] section of the config to False - * right arrow completion: the last readline history - line which the current line could complete to appears in gray and can - be completed by hitting the right arrow key, enabled in config with - right_arrow_completion thanks to Nicholas Sweeting - * fish shell style automatic reverse history search: up arrow searches - readline history for entries containing the contents of the current line - thanks to Nicholas Sweeting +* #234: copy to system keyboard with F10 +* fish style right arrow completion: the last readline history. + line which the current line could complete to appears in gray and can + be completed by hitting the right arrow key, enabled in config with + right_arrow_completion thanks to Nicholas Sweeting +* fish shell style automatic reverse history search: up arrow searches. + readline history for entries containing the contents of the current line + thanks to Nicholas Sweeting * incremental forward and reverse search, be default bound to meta-r and meta-s because the standard keys ctrl-r and ctrl-s have well established uses in bpython. These can be changed in the config for a more readline- @@ -46,10 +44,10 @@ New features: * Message displayed if history in scrollback buffer is inconsistent with output from last reevaluation of bpython session thanks to Susan Steinman * Adjust logging level with -L or -LL -* #426 mutliline autocompletion (requires optional dependency Jedi) -* #313 warn when undo may take cause extended delay, and prompt to undo +* #426: mutliline autocompletion (requires optional dependency Jedi) +* #313: warn when undo may take cause extended delay, and prompt to undo multiple lines (set threshold in config with single_undo_time) -* #382 partial support for pasting in text with blank lines +* #382: partial support for pasting in text with blank lines * basic string literal completion Fixes: @@ -81,13 +79,13 @@ up to the standards of bpython-curses: * unicode issues * curtsies properly suspends and resumes (ctrl-z, fg) * Assorted curtsies display issues fixed (#472, #468, #448, #440, #401, -#370) + #370) Python 2.6, 2.7, 3.3 and newer are supported. Support for 2.5 has been dropped. Furthermore, it is no longer necessary to run 2to3 on the source code. This release brings a lot more code coverage, a new contributing guide, -and so much more pep8 +and most of the code now conforms to PEP-8. Changes to dependencies: From 697ab2524ee45932b3cff5d906733f07d76ca994 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 22:17:31 +0100 Subject: [PATCH 0432/1650] Explain the purpose of the dependencies Signed-off-by: Sebastian Ramacher --- CHANGELOG | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d83aa43de..5b67b18dd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -90,8 +90,10 @@ and most of the code now conforms to PEP-8. Changes to dependencies: * greenlet and curtsies are no longer optional. -* six is new dependency. -* jedi and watchdog are new optional dependencies. +* six is a new dependency. +* jedi is a new optional dependency required for multiline completion. +* watchdog is a new optional dependency required for watching changes in + imported modules. 0.13.2 ------- From e9c7e2b30b29249f248e73a60a3f47180995a3ca Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 22:17:56 +0100 Subject: [PATCH 0433/1650] Merge fixes and parts of fixes in curtsies parts Signed-off-by: Sebastian Ramacher --- CHANGELOG | 47 ++++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5b67b18dd..034f93c26 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -52,34 +52,27 @@ New features: Fixes: -* #254: replacement box characters used for ascii terminals -* #284: __file__ is in scope after module run with bpython -i thanks to Lindsey -* #341: right arrow key suggestion color is configurable thanks to Alice Chen -* #347: Fix crash on unsafe autocompletion -* #349: writing newlines to stderr -* #363: banner no longer crashes bpython-urwid - thanks to Luca Barbato +* #254: Use ASCII characters if Unicode box characters are not supported by the + terminal. +* #284: __file__ is in scope after module run with bpython -i. Thanks to + Lindsey Raymond. +* #347: Fixed crash on unsafe autocompletion. +* #349: Fixed writing newlines to stderr. +* #363: Fixed banner crashing bpython-urwid. Thanks to Luca Barbato. * #366, #367: Fixed help() support in curtsies. -* #369: interactive sessions inherit compiler directives from files run with -i - interactive flag -* #391: fix crash when using meta-backspace thanks to Tony Wang -* #438, #450: bpython-curtsies startup behavior fixed - runs on startup, errors - during startup reported instead of crashing -* #447: fix behavior of duplicate keybindings -* #458: fix dictionary key completion crash in Python 2.6 thanks to Mary Mokuolu -* documenatation fixes from Lindsey Raymond - -There was a lot of assorted work bringing the curtsies frontend -up to the standards of bpython-curses: - -* match list dissapears correctly when single match is selected -* Filename completion working correctly -* dictionary key completion fixed and re-enabled -* pager for showing source and native help() viewing -* better status bar message display and clear code -* unicode issues -* curtsies properly suspends and resumes (ctrl-z, fg) -* Assorted curtsies display issues fixed (#472, #468, #448, #440, #401, - #370) +* #369: Interactive sessions inherit compiler directives from files run with -i + interactive flag. +* #370, #401, #440, #448, #468, #472: Fixed various display issues in curtsies. +* #391: Fixed crash when using Meta-backspace. Thanks to Tony Wang. +* #438, #450: bpython-curtsies startup behavior fixed. Errors + during startup are reported instead of crashing. +* #447: Fixed behavior of duplicate keybindings. +* #458: Fixed dictionary key completion crash in Python 2.6. Thanks to Mary + Mokuolu. +* Documenatation fixes from Lindsey Raymond. +* Fixed filename completion. +* Fixed various Unicode issues in curtsies. +* Fixed and re-enabled dictionary key completion in curtsies. Python 2.6, 2.7, 3.3 and newer are supported. Support for 2.5 has been dropped. Furthermore, it is no longer necessary to run 2to3 on the source code. From c7e33046298f22a13570995355a4b5d9c134d95c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 22:18:49 +0100 Subject: [PATCH 0434/1650] Sort new features by bug numbers Also remove some in depth explanation. This should be moved to the documentation. Signed-off-by: Sebastian Ramacher --- CHANGELOG | 69 +++++++++++++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 034f93c26..47e4094d7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,49 +6,42 @@ Changelog This release contains major changes to the frontends: -* curtsies is the new default frontend -* the old curses frontend is available as bpython-curses -* the GTK+ frontend has been removed +* curtsies is the new default frontend. +* The old curses frontend is available as bpython-curses. +* The GTK+ frontend has been removed. New features: +* #194: Syntax-highlighted tracebacks. Thanks to Miriam Lauter. +* #234: Copy to system keyboard. +* #285: Re-evaluate session and reimport modules. +* #313: Warn when undo may take cause extended delay, and prompt to undo + multiple lines. +* #322: Watch imported modules for changes and re-evaluate on changes. +* #328: bpython history not re-evaluated to edit a previous line of a multiline + statement. +* #334: readline command Meta-. for yank last argument. Thanks to Susan + Steinman and Steph Samson. +* #338: bpython help with F1. +* #354: Edit config file from within bpython. +* #382: Partial support for pasting in text with blank lines. +* #410: Startup banner that shows Python and bpython version +* #426: Experimental mutliline autocompletion. +* fish style last history completion with Arrow Right. Thanks to Nicholas + Sweeting. +* fish style automatic reverse history search with Arrow Up. + Thanks to Nicholas Sweeting. +* Incremental forward and reverse search. +* All readline keys which kill/cut text correctly copy text for paste + with Ctrl-y or Meta-y. * French translation. -* #338: bpython help with F1 -* #354: edit config file from within bpython with F3 -* #322: watch imported modules for changes and reevaluate on changes - which requires optional dependency "watchdog" Toggle with F5 -* #285: reevaluate session and reimport modules with F6 -* #234: copy to system keyboard with F10 -* fish style right arrow completion: the last readline history. - line which the current line could complete to appears in gray and can - be completed by hitting the right arrow key, enabled in config with - right_arrow_completion thanks to Nicholas Sweeting -* fish shell style automatic reverse history search: up arrow searches. - readline history for entries containing the contents of the current line - thanks to Nicholas Sweeting -* incremental forward and reverse search, be default bound to meta-r and - meta-s because the standard keys ctrl-r and ctrl-s have well established - uses in bpython. These can be changed in the config for a more readline- - faithful bpython experience. -* #410: startup banner that shows Python and bpython version -* #334: readline command meta-. for yank last argument works - thanks to Susan Steinman and Steph Samson -* all readline keys which kill/cut text correctly copy text for paste - with ctrl-y or meta-y -* #194: Syntax-highlighted tracebacks thanks to Miriam Lauter -* #328: bpython history not reevaluated to edit a previous line of a - multiline statement -* removal links provided for bpaste pastebins -* more informative error messages when source can't be found for an object - thanks to Liudmila Nikolaeva and Miriam +* Removal links for bpaste pastebins are now displayed. +* More informative error messages when source cannot be found for an object. + Thanks to Liudmila Nikolaeva and Miriam Lauter. * Message displayed if history in scrollback buffer is inconsistent with - output from last reevaluation of bpython session thanks to Susan Steinman -* Adjust logging level with -L or -LL -* #426: mutliline autocompletion (requires optional dependency Jedi) -* #313: warn when undo may take cause extended delay, and prompt to undo - multiple lines (set threshold in config with single_undo_time) -* #382: partial support for pasting in text with blank lines -* basic string literal completion + output from last re-evaluation of bpython session. Thanks to Susan Steinman. +* Adjust logging level with -L or -LL. +* Basic string literal completion. Fixes: From 63517a756f9a0aaecb7bbecef04556b101f4daa0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 22:20:29 +0100 Subject: [PATCH 0435/1650] Update dependency information Signed-off-by: Sebastian Ramacher --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c3708951f..bf7a26978 100644 --- a/README.rst +++ b/README.rst @@ -10,12 +10,13 @@ Dependencies * Pygments * requests -* curtsies >= 0.1.15,<0.2.0 +* curtsies >= 0.1.18,<0.2.0 * greenlet * Sphinx != 1.1.2 (optional, for the documentation) * mock (optional, for the testsuite) * babel (optional, for internationalization) * watchdog (optional, for monitoring imported modules for changes) +* jedi (optional, for experimental multiline completion) If you are using Python 2 on Mac OS X, the following dependencies are required as well: From 22f2aaa270936a93754390dd8b6d0c8cd8c0a483 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 22:37:08 +0100 Subject: [PATCH 0436/1650] Sort values Signed-off-by: Sebastian Ramacher --- bpython/config.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 711996568..74b0daa99 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -67,59 +67,60 @@ def loadini(struct, configfile): 'general': { 'arg_spec': True, 'auto_display_list': True, + 'autocomplete_mode': default_completion, 'color_scheme': 'default', 'complete_magic_methods': True, - 'autocomplete_mode': default_completion, 'dedent_after': 1, + 'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')), 'flush_output': True, 'highlight_show_source': True, + 'hist_duplicates': True, 'hist_file': '~/.pythonhist', 'hist_length': 100, - 'hist_duplicates': True, 'paste_time': 0.02, - 'single_undo_time': 1.0, - 'syntax': True, - 'tab_length': 4, 'pastebin_confirm': True, - 'pastebin_url': 'https://bpaste.net/json/new', - 'pastebin_show_url': 'https://bpaste.net/show/$paste_id', - 'pastebin_removal_url': 'https://bpaste.net/remove/$removal_id', 'pastebin_expiry': '1week', 'pastebin_helper': '', + 'pastebin_removal_url': 'https://bpaste.net/remove/$removal_id', + 'pastebin_show_url': 'https://bpaste.net/show/$paste_id', + 'pastebin_url': 'https://bpaste.net/json/new', 'save_append_py': False, - 'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')), + 'single_undo_time': 1.0, + 'syntax': True, + 'tab_length': 4, 'unicode_box': True }, 'keyboard': { 'backspace': 'C-h', - 'left': 'C-b', - 'right': 'C-f', 'beginning_of_line': 'C-a', - 'end_of_line': 'C-e', - 'transpose_chars': 'C-t', 'clear_line': 'C-u', 'clear_screen': 'C-l', 'clear_word': 'C-w', + 'copy_clipboard': 'F10', 'cut_to_buffer': 'C-k', 'delete': 'C-d', 'down_one_line': 'C-n', - 'exit': '', - 'external_editor': 'F7', 'edit_config': 'F3', 'edit_current_block': 'C-x', + 'end_of_line': 'C-e', + 'exit': '', + 'external_editor': 'F7', 'help': 'F1', 'last_output': 'F9', - 'copy_clipboard': 'F10', + 'left': 'C-b', 'pastebin': 'F8', + 'reimport': 'F6', + 'right': 'C-f', 'save': 'C-s', + 'search': 'C-o', 'show_source': 'F2', 'suspend': 'C-z', 'toggle_file_watch': 'F5', + 'transpose_chars': 'C-t', 'undo': 'C-r', - 'reimport': 'F6', - 'search': 'C-o', 'up_one_line': 'C-p', - 'yank_from_buffer': 'C-y'}, + 'yank_from_buffer': 'C-y' + }, 'cli': { 'suggestion_width': 0.8, 'trim_prompts': False, From 2e3fea6725c3073377cc164cfc01572788acd2f6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 22:52:37 +0100 Subject: [PATCH 0437/1650] Update documentation TODO: dedent_after Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/community.rst | 16 +-- doc/sphinx/source/configuration-options.rst | 120 ++++++++++++-------- doc/sphinx/source/contributing.rst | 5 +- 3 files changed, 81 insertions(+), 60 deletions(-) diff --git a/doc/sphinx/source/community.rst b/doc/sphinx/source/community.rst index 2d1be5c98..00b8c4093 100644 --- a/doc/sphinx/source/community.rst +++ b/doc/sphinx/source/community.rst @@ -4,22 +4,22 @@ Community ========= Do you need help with using bpython? Do you want to thank the contributors personally? Or maybe you want to help out, contribute some code or resources -or want to help in making bpython known to other persons? +or want to help in making bpython known to other persons? These are the places where you can find us. IRC --- -You can find us in #bpython on the Freenode network (irc.freenode.net). Don't -worry when you get no response (this does not usually happen) but we are all -from Europe and when you get to the channel during our nighttime you might have -to wait a while for a response. +You can find us in `#bpython `_ on the `Freenode +`_ network. Don't worry when you get no response (this does +not usually happen) but we are all from Europe and when you get to the channel +during our nighttime you might have to wait a while for a response. Mailing List ------------ -We have a mailing list at `google groups `_. -You can post questions there and releases are announced on the mailing -list. +We have a mailing list at `google groups +`_. You can post questions there and +releases are announced on the mailing list. Website ------- diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 46483b81c..7bad6f100 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -3,6 +3,11 @@ General This refers to the ``[general]`` section in your `$XDG_CONFIG_HOME/bpython/config` file. +arg_spec +^^^^^^^^ +Display the arg spec (list of arguments) for callables, when possible (default: +True). + auto_display_list ^^^^^^^^^^^^^^^^^ Display the autocomplete list as you type (default: True). @@ -16,48 +21,59 @@ subsequence, and fuzzy matches methods with common characters (default: simple). .. versionadded:: 0.12 -syntax +.. _configuration_color_scheme: + +color_scheme +^^^^^^^^^^^^ +See :ref:`themes` for more information. + +Color schemes should be put in ``$XDG_CONFIG_HOME/bpython/``. For example, to +use the theme ``$XDG_CONFIG_HOME/bpython/foo.theme`` set ``color_scheme = foo`` + +Leave blank or set to "default" to use the default (builtin) theme. + +complete_magic_methods +^^^^^^^^^^^^^^^^^^^^^^ +Whether magic methods should be auto completed (default: True). + +dedent_after +^^^^^^^^^^^^ +TODO (default: 1) + +editor ^^^^^^ -Syntax highlighting as you type (default: True). +Editor for externally editing the current line. -arg_spec -^^^^^^^^ -Display the arg spec (list of arguments) for callables, when possible (default: -True). +.. versionadded:: 0.13 + +flush_output +^^^^^^^^^^^^ +Whether to flush all output to stdout on exit (default: True). + +highlight_show_source +^^^^^^^^^^^^^^^^^^^^^ +Whether the source code of an object should be highlighted (default: True). + +hist_duplicates +^^^^^^^^^^^^^^^ +Whether to store duplicate entries in the history (default: True). hist_file ^^^^^^^^^ History file (default: ``~/.pythonhist``). -paste_time -^^^^^^^^^^ -The time between lines before pastemode is activated in seconds (default: 0.02). - hist_length ^^^^^^^^^^^ Number of lines to store in history (set to 0 to disable) (default: 100). -tab_length +paste_time ^^^^^^^^^^ -Soft tab size (default 4, see pep-8) - -pastebin_url -^^^^^^^^^^^^ -The pastebin url to post to (without a trailing slash). This pastebin has to be -a pastebin which uses provides a similar interface to ``bpaste.net``'s JSON -interface (default: https://bpaste.net/json/new). - -pastebin_show_url -^^^^^^^^^^^^^^^^^ -The url under which the new paste can be reached. ``$paste_id`` will be replaced -by the ID of the new paste (default: https://bpaste.net/show/$paste_id/). - -pastebin_removal_url -^^^^^^^^^^^^^^^^^^^^ -The url under which a paste can be removed. ``$removal_id`` will be replaced by -the removal ID of the paste (default: https://bpaste.net/remova/$removal_id/). +The time between lines before pastemode is activated in seconds (default: 0.02). -.. versionadded:: 0.14 +pastebin_confirm +^^^^^^^^^^^^^^^^ +Whether pasting to a pastebin needs to be confirmed before sending the data +(default: True). pastebin_expiry ^^^^^^^^^^^^^^^ @@ -120,29 +136,23 @@ following helper program can be used to create `gists .. versionadded:: 0.12 +pastebin_show_url +^^^^^^^^^^^^^^^^^ +The url under which the new paste can be reached. ``$paste_id`` will be replaced +by the ID of the new paste (default: https://bpaste.net/show/$paste_id/). -single_undo_time -^^^^^^^^^^^^^^^^ -Time duration an undo must be predicted to take before prompting -to undo multiple lines at once. Use -1 to never prompt, or 0 to always prompt. -(default: 1.0) +pastebin_removal_url +^^^^^^^^^^^^^^^^^^^^ +The url under which a paste can be removed. ``$removal_id`` will be replaced by +the removal ID of the paste (default: https://bpaste.net/remova/$removal_id/). .. versionadded:: 0.14 -.. _configuration_color_scheme: - -color_scheme -^^^^^^^^^^^^ -See :ref:`themes` for more information. - -Color schemes should be put in ``$XDG_CONFIG_HOME/bpython/``. For example, to -use the theme ``$XDG_CONFIG_HOME/bpython/foo.theme`` set ``color_scheme = foo`` - -Leave blank or set to "default" to use the default (builtin) theme. - -flush_output +pastebin_url ^^^^^^^^^^^^ -Whether to flush all output to stdout on exit (default: True). +The pastebin url to post to (without a trailing slash). This pastebin has to be +a pastebin which uses provides a similar interface to ``bpaste.net``'s JSON +interface (default: https://bpaste.net/json/new). save_append_py ^^^^^^^^^^^^^^ @@ -150,11 +160,21 @@ Whether to append ``.py`` to the filename while saving the input to a file. .. versionadded:: 0.13 -editor +single_undo_time +^^^^^^^^^^^^^^^^ +Time duration an undo must be predicted to take before prompting +to undo multiple lines at once. Use -1 to never prompt, or 0 to always prompt. +(default: 1.0) + +.. versionadded:: 0.14 + +syntax ^^^^^^ -Editor for externally editing the current line. +Syntax highlighting as you type (default: True). -.. versionadded:: 0.13 +tab_length +^^^^^^^^^^ +Soft tab size (default 4, see PEP-8). unicode_box ^^^^^^^^^^^ diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index f92702ae0..73e2e85fd 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -10,8 +10,9 @@ these are particularly good ones to start out with. See our section about the :ref:`community` for a list of resources. -`#bpython` on freenode is particularly useful, but you might have to wait for a -while to get a question answered depending on the time of day. +`#bpython `_ on Freenode is particularly useful, +but you might have to wait for a while to get a question answered depending on +the time of day. Getting your development environment set up ------------------------------------------- From 4451c651eae493d0da94f52e5d42a53e8025fb30 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 10 Feb 2015 17:02:21 -0500 Subject: [PATCH 0438/1650] document dedent config option --- doc/sphinx/source/configuration-options.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 7bad6f100..96b67bd3a 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -38,7 +38,8 @@ Whether magic methods should be auto completed (default: True). dedent_after ^^^^^^^^^^^^ -TODO (default: 1) +Number of blank lines required before next line will be dedented (default: 1). +If set to 0, automatic dedenting never occurs. editor ^^^^^^ From 624852264c3d0a99c5fb339ad27ecd5f39a296d5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 23:15:22 +0100 Subject: [PATCH 0439/1650] Document key bindings Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/configuration-options.rst | 206 ++++++++++++++------ 1 file changed, 142 insertions(+), 64 deletions(-) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 96b67bd3a..64c1cd59d 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -198,64 +198,47 @@ Valid keys are: * Control + any alphanumeric character (C-a through A-z, also a few others). * Any function key ranging from F1 to F12. -pastebin -^^^^^^^^ -Default: F8 - -last_output -^^^^^^^^^^^ -Default: F9 - -Shows the last output in the systems $PAGER. - -reimport -^^^^^^^^ -Default: F6 - -Reruns entire session, reloading all modules by clearing the sys.modules cache. - -.. versionadded:: 0.14 - -help -^^^^ -Default: F1 +backspace +^^^^^^^^^ +Default: C-h -Brings up sincerely cheerful description of bpython features and current key bindings. +Delete character in front of the cursor. .. versionadded:: 0.14 -toggle_file_watch +beginning_of_line ^^^^^^^^^^^^^^^^^ -Default: F5 +Default: C-a -Toggles file watching behaviour; re-runs entire bpython session whenever an imported -module is modified. +Move to the beginning of the line. .. versionadded:: 0.14 -save -^^^^ -Default: C-s +clear_line +^^^^^^^^^^ +Default: C-u -Saves the current session to a file (prompts for filename) +Clears to the beginning of the line. -undo -^^^^ -Default: C-r +clear_screen +^^^^^^^^^^^^ +Default: C-l -Rewinds the last action. +Clears the screen to the top. -up_one_line -^^^^^^^^^^^ -Default: C-p +clear_word +^^^^^^^^^^ +Default: C-w -Move the cursor up, by one line. +Clear the word the cursor is currently on. -down_one_line -^^^^^^^^^^^^^ -Default: C-n +copy_clipboard +^^^^^^^^^^^^^^ +Default: F10 -Move the cursor down, by one line. +Copy to clipboard. + +.. versionadded:: 0.14 cut_to_buffer ^^^^^^^^^^^^^ @@ -263,41 +246,41 @@ Default: C-k Cuts the current line to the buffer. -search +delete ^^^^^^ -Default: C-o +Default: C-d -Search up for any lines containing what is on the current line. +Delete character under the cursor. -yank_from_buffer -^^^^^^^^^^^^^^^^ -Default: C-y +down_one_line +^^^^^^^^^^^^^ +Default: C-n -Pastes the current line from the buffer (the one you previously cutted) +Move the cursor down, by one line. -clear_word -^^^^^^^^^^ -Default: C-w +edit_config +^^^^^^^^^^^ +Default: F3 -Clear the word the cursor is currently on. +Edit bpython configuration in external editor. -clear_line -^^^^^^^^^^ -Default: C-u +.. versionadded:: 0.14 -Clears to the beginning of the line. +edit_current_block +^^^^^^^^^^^^^^^^^^ +Default: C-x -clear_screen -^^^^^^^^^^^^ -Default: C-l +Edit current block in external editor. -Clears the screen to the top. +.. versionadded:: 0.14 -show_source +end_of_line ^^^^^^^^^^^ -Default: F2 +Default: C-e -Shows the source of the currently being completed (python) function. +Move to the of the line. + +.. versionadded:: 0.14 exit ^^^^ @@ -313,6 +296,101 @@ Edit current line in an external editor. .. versionadded:: 0.13 +help +^^^^ +Default: F1 + +Brings up sincerely cheerful description of bpython features and current key bindings. + +.. versionadded:: 0.14 + +last_output +^^^^^^^^^^^ +Default: F9 + +Shows the last output in the systems $PAGER. + +left +^^^^ +Default: C-b + +Move a character to the left. + +.. versionadded:: 0.14 + +pastebin +^^^^^^^^ +Default: F8 + +reimport +^^^^^^^^ +Default: F6 + +Reruns entire session, reloading all modules by clearing the sys.modules cache. + +.. versionadded:: 0.14 + +right +^^^^^ +Default: C-f + +Move a character to the right. + +.. versionadded:: 0.14 + +save +^^^^ +Default: C-s + +Saves the current session to a file (prompts for filename) + +search +^^^^^^ +Default: C-o + +Search up for any lines containing what is on the current line. + +show_source +^^^^^^^^^^^ +Default: F2 + +Shows the source of the currently being completed (python) function. + +toggle_file_watch +^^^^^^^^^^^^^^^^^ +Default: F5 + +Toggles file watching behaviour; re-runs entire bpython session whenever an imported +module is modified. + +.. versionadded:: 0.14 + +transpose_chars +^^^^^^^^^^^^^^^ +Default: C-t + +Transpose current character with the one left of it. + +.. versionadded:: 0.14 + +undo +^^^^ +Default: C-r + +Rewinds the last action. + +up_one_line +^^^^^^^^^^^ +Default: C-p + +Move the cursor up, by one line. + +yank_from_buffer +^^^^^^^^^^^^^^^^ +Default: C-y + +Pastes the current line from the buffer (the one you previously cutted) + CLI --- This refers to the ``[cli]`` section in your config file. From f01cb423b4c8ee62621c3fb1610df61e12393ff8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 23:15:33 +0100 Subject: [PATCH 0440/1650] Remove handling of suspend key Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 589cf3090..e78100d19 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -596,8 +596,6 @@ def process_key_event(self, e): self.show_source() elif e in key_dispatch[self.config.help_key]: self.pager(self.help_text()) - elif e in key_dispatch[self.config.suspend_key]: - raise SystemExit() elif e in key_dispatch[self.config.exit_key]: raise SystemExit() elif e in ("\n", "\r", "", "", ""): From e85e2af5a39d2145f3a7fc84cd55ec6b85dbb255 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 23:31:47 +0100 Subject: [PATCH 0441/1650] Include sample config Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/configuration-options.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 64c1cd59d..48a0e6fa6 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -433,3 +433,9 @@ When the cursor is at the end of a line, pressing right arrow or ctrl-f will complete the full line. This option also turns on substring history search, highlighting the matching section in previous result. + +Sample config +------------- + +.. include:: ../../../bpython/sample-config + :literal: From 47a85199d276a399fd0b88933feeb56c05120ad2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Feb 2015 23:36:28 +0100 Subject: [PATCH 0442/1650] Use AUTHORS in authors.rst Signed-off-by: Sebastian Ramacher --- AUTHORS | 4 +++- doc/sphinx/source/authors.rst | 26 +------------------------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/AUTHORS b/AUTHORS index c873f79a8..230128c96 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,4 +24,6 @@ Other contributors are (in alphabetical order): * Tarek Ziade * Marien Zwart -Many thanks for all contributions! +A big thanks goes out to all the people who help us out by either submitting +patches, helping us determine problems, our package maintainers, and of course +everybody who creates issues for us to fix. diff --git a/doc/sphinx/source/authors.rst b/doc/sphinx/source/authors.rst index 6c3ae627a..c83e6aefb 100644 --- a/doc/sphinx/source/authors.rst +++ b/doc/sphinx/source/authors.rst @@ -5,28 +5,4 @@ Authors If you contributed to bpython and want to be on this list please find us (:ref:`community`) and let us know! -bpython is written and maintained by Bob Farrell -. - -Other contributors are (in alphabetical order): - -* Thomas Ballinger -* Federico Ceratto -* Ingrid Cheung -* Maja Frydrychowicz -* Martha Girdler -* Eike Hein -* Allison Kaptur -* Jason Laster -* Brandon Navra -* Michele Orrù -* Pavel Panchekha -* Sebastian Ramacher -* Amjith Ramanujam -* Andreas Stührk -* Simon de Vlieger -* Marien Zwart - -A big thanks goes out to all the people who help us out by either submitting -patches, helping us determine problems, our package maintainers, and of course -everybody who creates issues for us to fix. +.. include:: ../../../AUTHORS From 2d2815071655eb2836db7494fa8f486e942e3e0f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 11 Feb 2015 17:51:41 -0500 Subject: [PATCH 0443/1650] fix bug with sending session to external editor --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index e78100d19..4ea5fac16 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -789,7 +789,7 @@ def send_current_block_to_external_editor(self, filename=None): def send_session_to_external_editor(self, filename=None): for_editor = (u"### current bpython session - file will be " - u"reevaluated, ### lines will not be run\n'") + u"reevaluated, ### lines will not be run\n") for_editor += u'\n'.join(line[len(self.ps1):] if line.startswith(self.ps1) else line[len(self.ps2):] From 3135a17f5ca77242c02030baf207a2320fb53774 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 12 Feb 2015 11:20:07 -0500 Subject: [PATCH 0444/1650] update docs feature descriptions in keybinds, spelling, windows instructions --- doc/sphinx/source/configuration-options.rst | 18 +++++++++++------- doc/sphinx/source/configuration.rst | 3 ++- doc/sphinx/source/themes.rst | 2 +- doc/sphinx/source/windows.rst | 4 ++-- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 48a0e6fa6..07467ce78 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -19,6 +19,8 @@ There are three modes for autocomplete. simple, substring, and fuzzy. Simple matches methods with a common prefix, substring matches methods with a common subsequence, and fuzzy matches methods with common characters (default: simple). +As of version 0.14 this option has no effect, but is reserved for later use. + .. versionadded:: 0.12 .. _configuration_color_scheme: @@ -43,7 +45,7 @@ If set to 0, automatic dedenting never occurs. editor ^^^^^^ -Editor for externally editing the current line. +Editor for externally editing the current line, session, or config file. .. versionadded:: 0.13 @@ -51,9 +53,11 @@ flush_output ^^^^^^^^^^^^ Whether to flush all output to stdout on exit (default: True). +Only relevant to bpython-curses and bpython-urwid. + highlight_show_source ^^^^^^^^^^^^^^^^^^^^^ -Whether the source code of an object should be highlighted (default: True). +Whether the source code of an object should be syntax highlighted (default: True). hist_duplicates ^^^^^^^^^^^^^^^ @@ -69,7 +73,7 @@ Number of lines to store in history (set to 0 to disable) (default: 100). paste_time ^^^^^^^^^^ -The time between lines before pastemode is activated in seconds (default: 0.02). +The time between keypresses before pastemode is deactivated in bpython-curses (default: 0.02). pastebin_confirm ^^^^^^^^^^^^^^^^ @@ -195,7 +199,7 @@ telling you the key does not exist in bpython.keys. Valid keys are: -* Control + any alphanumeric character (C-a through A-z, also a few others). +* Control + any alphanumeric character (C-a through C-z, also a few others). * Any function key ranging from F1 to F12. backspace @@ -236,7 +240,7 @@ copy_clipboard ^^^^^^^^^^^^^^ Default: F10 -Copy to clipboard. +Copy the entire session to clipboard. .. versionadded:: 0.14 @@ -292,7 +296,7 @@ external_editor ^^^^^^^^^^^^^^^ Default: F7 -Edit current line in an external editor. +Edit the entire session in an external editor. .. versionadded:: 0.13 @@ -308,7 +312,7 @@ last_output ^^^^^^^^^^^ Default: F9 -Shows the last output in the systems $PAGER. +Shows the last output in the systems $PAGER. Only works in bpython-curses. left ^^^^ diff --git a/doc/sphinx/source/configuration.rst b/doc/sphinx/source/configuration.rst index e7911e482..6b07f8a6e 100644 --- a/doc/sphinx/source/configuration.rst +++ b/doc/sphinx/source/configuration.rst @@ -6,6 +6,7 @@ You can edit the config file by pressing F3 (default). If a config file does not exist you will asked if you would like to create a file. By default it will be saved to ``$XDG_CONFIG_HOME/.config/bpython/config`` [#f1]_. +.. include:: configuration-options.rst + .. :: Footnotes .. [#f1] ``$XDG_CONFIG_HOME`` defaults to ``~/.config`` if not set. -.. include:: configuration-options.rst diff --git a/doc/sphinx/source/themes.rst b/doc/sphinx/source/themes.rst index c28875be4..99a9f0f63 100644 --- a/doc/sphinx/source/themes.rst +++ b/doc/sphinx/source/themes.rst @@ -2,7 +2,7 @@ Themes ====== -This chapter is about bpython's themeing capabalities. +This chapter is about bpython's theming capabalities. bpython uses .theme files placed in your ``$XDG_CONFIG_HOME/bpython`` directory [#f1]_. You can set the theme in the :ref:`configuration_color_scheme` option diff --git a/doc/sphinx/source/windows.rst b/doc/sphinx/source/windows.rst index c2e71bc6b..6d2c05a0b 100644 --- a/doc/sphinx/source/windows.rst +++ b/doc/sphinx/source/windows.rst @@ -11,5 +11,5 @@ we plan on providing in the future). The easiest way to get `bpython.cli` (the curses frontend running) is to install an unofficial windows binary for pdcurses from: http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses. After this you can just -`pip install bpython` and run bpython like you would on a Linux system (e.g. -by typing `bpython` on your prompt). +`pip install bpython` and run bpython curses frontend like you would on a Linux +system (e.g. by typing `bpython-curses` on your prompt). From eae09016ebc32b04743c2d85e9683fdea7db46f6 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Thu, 12 Feb 2015 17:22:11 +0100 Subject: [PATCH 0445/1650] remove sourcecode links --- doc/sphinx/source/bpaste.rst | 5 +-- doc/sphinx/source/index.rst | 1 - doc/sphinx/source/sourcecode.rst | 64 -------------------------------- 3 files changed, 2 insertions(+), 68 deletions(-) delete mode 100644 doc/sphinx/source/sourcecode.rst diff --git a/doc/sphinx/source/bpaste.rst b/doc/sphinx/source/bpaste.rst index 9f33bb3e0..a91ef6317 100644 --- a/doc/sphinx/source/bpaste.rst +++ b/doc/sphinx/source/bpaste.rst @@ -7,6 +7,5 @@ configured by default to paste to this pastebin. Removal ------- -If you want a paste removed from the pastebin you can email Simon at -simon@ikanobori.jp and he will remove the paste for you, be sure to mention the -paste URL. +If you want a paste removed from the pastebin you can use the removal link as +shown by bpython. Refer to https://bpaste.net/removal if you lost it. diff --git a/doc/sphinx/source/index.rst b/doc/sphinx/source/index.rst index 0c77a79bd..3322f2c6c 100644 --- a/doc/sphinx/source/index.rst +++ b/doc/sphinx/source/index.rst @@ -20,7 +20,6 @@ Contents: django windows changelog - sourcecode bpaste tips bpdb diff --git a/doc/sphinx/source/sourcecode.rst b/doc/sphinx/source/sourcecode.rst deleted file mode 100644 index bdd9b5cc7..000000000 --- a/doc/sphinx/source/sourcecode.rst +++ /dev/null @@ -1,64 +0,0 @@ -.. _sourcecode: - -Sourcecode -========== - -Warning, large parts of source code are still undocumented till we include -the automatic generation of this documentation by adding in restructed text -comments. - -bpython.cli ------------ - -.. module:: cli - :platform: POSIX - :synopsis: Basic interpreter. - -.. function:: log(x) - - Function to log anything in x to /tmp/bpython.log - -.. function:: parsekeywordpairs(signature) - - Not documented yet. - - :param signature: string - :rtype: dictionary - -.. function:: fixlongargs(f, argspec) - - Functions taking default arguments that are references to other objects - whose str() is too big will cause breakage, so we swap out the object - itself with the name it was referenced with in the source by parsing the - source itself ! - -.. class:: FakeStdin - -.. method:: FakeStdin.__init__(self, interface) - - Take the curses Repl on init and assume it provides a get_key method - which, fortunately, it does.""" - -.. method:: FakeStdin.isatty(self) - - Spoof into thinking this is a tty - - :rtype: Boolean - :returns: True - - -.. method:: FakeStdin.readline(self) - - I can't think of any reason why anything other than readline would - be useful in the context of an interactive interpreter so this is the - only one I've done anything with. The others are just there in case - someone does something weird to stop it from blowing up.""" - - :rtype: string - -bpython.keys ------------- - -.. module:: keys - :platform: POSIX - :synopsis: Keyboard mappings From 1451c2cd978a032729e3a1a414a361946bc75c4f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 12 Feb 2015 21:26:54 +0100 Subject: [PATCH 0446/1650] Fix another occurrence of events.RefreshRequestEvent Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 4ea5fac16..3cd8fd60e 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1286,7 +1286,7 @@ def dumb_input(self, requested_refreshes=[]): while chars or requested_refreshes: if requested_refreshes: requested_refreshes.pop() - self.process_event(events.RefreshRequestEvent()) + self.process_event(bpythonevents.RefreshRequestEvent()) continue c = chars.pop(0) if c in '/': From 4bacc37236bf8cbe761a55cb56abdf9f79a62950 Mon Sep 17 00:00:00 2001 From: Mary Mokuolu Date: Thu, 12 Feb 2015 15:58:38 -0500 Subject: [PATCH 0447/1650] Add test for param completion --- bpython/test/test_autocomplete.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 6c0b39092..ed8a7c449 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -1,4 +1,6 @@ from collections import namedtuple +import inspect +from bpython._py3compat import py3 try: import unittest2 as unittest @@ -256,3 +258,21 @@ def test_completions_starting_with_different_cases(self): 2, ' a', 'class Foo:\n a', ['adsf'], [Comp('Abc', 'bc'), Comp('ade', 'de')]) self.assertSetEqual(matches, set(['ade'])) + + +class TestParameterNameCompletion(unittest.TestCase): + def test_set_of_params_returns_when_matches_found(self): + def func(apple, apricot, banana, carrot): + pass + if py3: + argspec = list(inspect.getfullargspec(func)) + else: + argspec = list(inspect.getargspec(func)) + + argspec = ["func", argspec, False] + com=autocomplete.ParameterNameCompletion() + self.assertSetEqual(com.matches(1, "a", argspec=argspec), set(['apple=', 'apricot='])) + self.assertSetEqual(com.matches(2, "ba", argspec=argspec), set(['banana='])) + self.assertSetEqual(com.matches(3, "car", argspec=argspec), set(['carrot='])) + + From c663b20d06dfbfb739f75369f1d4f63a9332c61a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 12 Feb 2015 22:13:36 +0100 Subject: [PATCH 0448/1650] Run the test Signed-off-by: Sebastian Ramacher --- bpython/test/test_interpreter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index a7fcdd118..3474ca5fc 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -70,15 +70,15 @@ def test_runsource_bytes(self): self.assertEqual(i.locals['b'], "\xfe") @unittest.skipUnless(py3, "Only a syntax error in Python 3") - @mock.patch.object(interpreter.Interp, 'showsyntaxerror') - def test_runsource_bytes_over_128_syntax_error(self): + def test_runsource_bytes_over_128_syntax_error_py3(self): i = interpreter.Interp(encoding='latin-1') + i.showsyntaxerror = mock.Mock(return_value=None) i.runsource("a = b'\xfe'", encode=True) - i.showsyntaxerror.assert_called_with() + i.showsyntaxerror.assert_called_with(mock.ANY) @unittest.skipIf(py3, "encode is Python 2 only") - def test_runsource_bytes_over_128_syntax_error(self): + def test_runsource_bytes_over_128_syntax_error_py2(self): i = interpreter.Interp(encoding='latin-1') i.runsource("a = b'\xfe'", encode=True) From 55383675413ad4b75ccc9bf98c89650bb35ac19a Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 12 Feb 2015 19:03:11 -0500 Subject: [PATCH 0449/1650] fixes to changelog --- CHANGELOG | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 47e4094d7..e7402b5ae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,7 +41,7 @@ New features: * Message displayed if history in scrollback buffer is inconsistent with output from last re-evaluation of bpython session. Thanks to Susan Steinman. * Adjust logging level with -L or -LL. -* Basic string literal completion. +* String literal attribute completion. Fixes: @@ -59,10 +59,10 @@ Fixes: * #391: Fixed crash when using Meta-backspace. Thanks to Tony Wang. * #438, #450: bpython-curtsies startup behavior fixed. Errors during startup are reported instead of crashing. -* #447: Fixed behavior of duplicate keybindings. +* #447: Fixed behavior of duplicate keybindings. Thanks to Keyan Pishdadian. * #458: Fixed dictionary key completion crash in Python 2.6. Thanks to Mary Mokuolu. -* Documenatation fixes from Lindsey Raymond. +* Documentation fixes from Lindsey Raymond. * Fixed filename completion. * Fixed various Unicode issues in curtsies. * Fixed and re-enabled dictionary key completion in curtsies. From 3332f4ff9059ce777fb1b2a1b0f27b6b6a0a1e38 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 12 Feb 2015 22:28:33 -0500 Subject: [PATCH 0450/1650] fix typo in version message --- bpython/args.py | 2 +- bpython/test/test_args.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/bpython/args.py b/bpython/args.py index da02d56c3..6f2e9ef29 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -88,7 +88,7 @@ def parse(args, extras=None, ignore_stdin=False): os.execv(sys.executable, [sys.executable] + args) if options.version: - print('bpython version', __version__, eof="") + print('bpython version', __version__, end=" ") print('on top of Python', sys.version.split()[0]) print('(C) 2008-2015 Bob Farrell, Andreas Stuehrk et al. ' 'See AUTHORS for detail.') diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index a8e568ae2..04bbd9d4d 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -3,6 +3,9 @@ import tempfile from textwrap import dedent +from bpython import args +from bpython.test import FixLanguageTestCase as TestCase + try: import unittest2 as unittest except ImportError: @@ -31,3 +34,14 @@ def test_exec_dunder_file(self): (_, stderr) = p.communicate() self.assertEquals(stderr.strip(), f.name) + + +class TestParse(TestCase): + + def test_version(self): + try: + args.parse(['--version']) + except SystemExit: + pass + else: + self.fail('Should have raise SystemExit') From b51663c3e6326b572fd03e2fa545f0f60a3589c4 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 12 Feb 2015 22:59:09 -0500 Subject: [PATCH 0451/1650] test last_word, add failing test case that caused crash --- bpython/curtsiesfrontend/repl.py | 11 ++++++----- bpython/test/test_curtsies_repl.py | 6 ++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3cd8fd60e..afca43e60 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -628,11 +628,8 @@ def process_key_event(self, e): def get_last_word(self): - def last_word(line): - return line.split().pop() if line else '' - - previous_word = last_word(self.rl_history.entry) - word = last_word(self.rl_history.back()) + previous_word = _last_word(self.rl_history.entry) + word = _last_word(self.rl_history.back()) line = self.current_line self._set_current_line(line[:len(line) - len(previous_word)] + word, reset_rl_history=False) @@ -1522,6 +1519,10 @@ def tabs_to_spaces(line): return line.replace('\t', ' ') +def _last_word(line): + return line.split().pop() if line else '' + + def compress_paste_event(paste_event): """If all events in a paste event are identical and not simple characters, returns one of them diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 8b3e6b177..36b3546ef 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -77,6 +77,12 @@ def test_get_last_word(self): self.repl.get_last_word() self.assertEqual(self.repl.current_line, 'abcde3') + def test_last_word(self): + self.assertEquals(curtsiesrepl._last_word(''), '') + self.assertEquals(curtsiesrepl._last_word(' '), '') + self.assertEquals(curtsiesrepl._last_word('a'), 'a') + self.assertEquals(curtsiesrepl._last_word('a b'), 'b') + # this is the behavior of bash - not currently implemented @unittest.skip def test_get_last_word_with_prev_line(self): From 0ee753a630668ff463d211087467c5b055adbbb0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 12 Feb 2015 22:59:36 -0500 Subject: [PATCH 0452/1650] fix crash due to calling last_word on a line with only whitespace --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index afca43e60..54f83873c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1520,7 +1520,7 @@ def tabs_to_spaces(line): def _last_word(line): - return line.split().pop() if line else '' + return line.split().pop() if line.split() else '' def compress_paste_event(paste_event): From 8fd2e00625bef7cd591987c5876d58daaac86931 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 13 Feb 2015 12:25:58 -0500 Subject: [PATCH 0453/1650] Add curtsies-specific options to man page --- doc/sphinx/source/man-bpython.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/doc/sphinx/source/man-bpython.rst b/doc/sphinx/source/man-bpython.rst index 6a0fa8398..7333ec881 100644 --- a/doc/sphinx/source/man-bpython.rst +++ b/doc/sphinx/source/man-bpython.rst @@ -50,12 +50,15 @@ If :program:`bpython` sees an argument it does not know, execution falls back to the regular Python interpreter. ---config= Use instead of default config file. --h, --help Show the help message and exit. --i, --interactive Drop to bpython shell after running file instead of exiting. - The PYTHONSTARTUP file is not read. --q, --quiet Do not flush the output to stdout. --V, --version Print :program:`bpython`'s version and exit. +--config= Use instead of default config file. +-h, --help Show the help message and exit. +-i, --interactive Drop to bpython shell after running file instead of exiting. + The PYTHONSTARTUP file is not read. +-q, --quiet Do not flush the output to stdout. +-V, --version Print :program:`bpython`'s version and exit. +-L, --log Write debugging messages to the file bpython.log. Use + -LL for more verbose logging. Only available in :program:`bpython`. +-t file, --type=file Paste in the contents of a file at startup. Only available in :program:`bpython`. In addition to the above options, :program:`bpython-urwid` also supports the following options if Twisted is available: @@ -69,7 +72,7 @@ following options if Twisted is available: plugins. Use -- to pass options to the plugin. -s , --server= Run an eval server on port . This - options forces the use of a Twisted reactor. + option forces the use of a Twisted reactor. Keys ---- From 8db86624378d2924e9ea72b6bbc650ab2c29c194 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 14 Feb 2015 22:55:18 +0100 Subject: [PATCH 0454/1650] Update manpage Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/man-bpython.rst | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/sphinx/source/man-bpython.rst b/doc/sphinx/source/man-bpython.rst index 7333ec881..e18060d3f 100644 --- a/doc/sphinx/source/man-bpython.rst +++ b/doc/sphinx/source/man-bpython.rst @@ -35,7 +35,7 @@ Rewind. functions. Pastebin code/write to file. - This posts the current buffer to a pastebin (paste.pocoo.org) or writes it + This posts the current buffer to a pastebin (bpaste.net) or writes it to a file. Flush curses screen to stdout. @@ -49,18 +49,23 @@ The long and short forms of options, shown here as alternatives, are equivalent. If :program:`bpython` sees an argument it does not know, execution falls back to the regular Python interpreter. +The following options are supported by all frontends: --config= Use instead of default config file. -h, --help Show the help message and exit. --i, --interactive Drop to bpython shell after running file instead of exiting. - The PYTHONSTARTUP file is not read. +-i, --interactive Drop to bpython shell after running file instead of + exiting. The PYTHONSTARTUP file is not read. -q, --quiet Do not flush the output to stdout. -V, --version Print :program:`bpython`'s version and exit. + +In addition to the above options, :program:`bpython` also supports the following +options: + -L, --log Write debugging messages to the file bpython.log. Use - -LL for more verbose logging. Only available in :program:`bpython`. --t file, --type=file Paste in the contents of a file at startup. Only available in :program:`bpython`. + -LL for more verbose logging. +-t file, --type=file Paste in the contents of a file at startup. -In addition to the above options, :program:`bpython-urwid` also supports the +In addition to the common options, :program:`bpython-urwid` also supports the following options if Twisted is available: -r , --reactor= Use Twisted's instead of urwid's From 64bb2dd7826bf4472b7c05e3fb8937fd9077d276 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 13 Feb 2015 12:33:15 -0500 Subject: [PATCH 0455/1650] change --type to --paste for initial paste --- bpython/curtsies.py | 6 +++--- doc/sphinx/source/man-bpython.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index f3c25cdae..ba6ea2b5f 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -34,8 +34,8 @@ def main(args=None, locals_=None, banner=None): 'curtsies options', None, [ Option('--log', '-L', action='count', help=_("log debug messages to bpython.log")), - Option('--type', '-t', action='store_true', - help=_("enter lines of file as though interactively " + Option('--paste', '-p', action='store_true', + help=_("start by pasting lines of a file into session" "typed")), ])) if options.log is None: @@ -57,7 +57,7 @@ def main(args=None, locals_=None, banner=None): if not options: raise ValueError("don't pass in exec_args without options") exit_value = 0 - if options.type: + if options.paste: paste = curtsies.events.PasteEvent() encoding = inspection.get_encoding_file(exec_args[0]) with io.open(exec_args[0], encoding=encoding) as f: diff --git a/doc/sphinx/source/man-bpython.rst b/doc/sphinx/source/man-bpython.rst index e18060d3f..1d87b5582 100644 --- a/doc/sphinx/source/man-bpython.rst +++ b/doc/sphinx/source/man-bpython.rst @@ -63,7 +63,7 @@ options: -L, --log Write debugging messages to the file bpython.log. Use -LL for more verbose logging. --t file, --type=file Paste in the contents of a file at startup. +-p file, --paste=file Paste in the contents of a file at startup. Only available in :program:`bpython`. In addition to the common options, :program:`bpython-urwid` also supports the following options if Twisted is available: From a8f58590709ab58700614de1f3de391acfce957b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 15 Feb 2015 20:53:04 -0500 Subject: [PATCH 0456/1650] man page docs linewrap --- doc/sphinx/source/man-bpython.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/sphinx/source/man-bpython.rst b/doc/sphinx/source/man-bpython.rst index 1d87b5582..756180836 100644 --- a/doc/sphinx/source/man-bpython.rst +++ b/doc/sphinx/source/man-bpython.rst @@ -63,7 +63,8 @@ options: -L, --log Write debugging messages to the file bpython.log. Use -LL for more verbose logging. --p file, --paste=file Paste in the contents of a file at startup. Only available in :program:`bpython`. +-p file, --paste=file Paste in the contents of a file at startup. + Only available in :program:`bpython`. In addition to the common options, :program:`bpython-urwid` also supports the following options if Twisted is available: From 851531daffa1e6e03fd7d889f5bcebe5a7fb5997 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Feb 2015 03:05:41 +0100 Subject: [PATCH 0457/1650] Remove redundent part --- doc/sphinx/source/man-bpython.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/sphinx/source/man-bpython.rst b/doc/sphinx/source/man-bpython.rst index 756180836..951300c6c 100644 --- a/doc/sphinx/source/man-bpython.rst +++ b/doc/sphinx/source/man-bpython.rst @@ -64,7 +64,6 @@ options: -L, --log Write debugging messages to the file bpython.log. Use -LL for more verbose logging. -p file, --paste=file Paste in the contents of a file at startup. - Only available in :program:`bpython`. In addition to the common options, :program:`bpython-urwid` also supports the following options if Twisted is available: From a766ae72cf7b75be44c7e28c9d64220a0b9bbcf7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Feb 2015 20:06:18 +0100 Subject: [PATCH 0458/1650] Update copyright years Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py index 85fb27ee9..f0e6bce95 100644 --- a/doc/sphinx/source/conf.py +++ b/doc/sphinx/source/conf.py @@ -38,7 +38,7 @@ # General information about the project. project = u'bpython' -copyright = u'2008-2014 Bob Farrell, Andreas Stuehrk et al.' +copyright = u'2008-2015 Bob Farrell, Andreas Stuehrk et al.' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 88a2ca6a73cc30fac70fe72e06827a3b83b0347c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Feb 2015 20:06:33 +0100 Subject: [PATCH 0459/1650] PEP 440 versions Signed-off-by: Sebastian Ramacher --- setup.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index bdbedd372..7db296889 100755 --- a/setup.py +++ b/setup.py @@ -4,8 +4,9 @@ import os import platform -import sys +import re import subprocess +import sys from distutils.command.build import build from setuptools import setup @@ -34,6 +35,52 @@ # version handling + + +def git_describe_to_python_version(version): + """Convert output from git describe to PEP 440 conforming versions.""" + + version_info = version.split('-') + if len(version_info) < 2: + return 'unknown' + + # we always have $version-$release + release_type = version_info[1] + + version_data = { + 'version': version_info[0], + 'release_type': release_type, + } + if len(version_info) == 4: + version_data['commits'] = version_info[2] + else: + version_data['commits'] = 0 + + if release_type == 'release': + if len(version_info) == 2: + # format: $version-release + # This is the case at time of the release. + fmt = '{version}' + elif len(version_info) == 4: + # format: $version-release-$commits-$hash + # This is the case after a release. + fmt = '{version}-{commits}' + elif release_type == 'dev': + # format: $version-dev-$commits-$hash or $version-dev + fmt = '{version}.dev{commits}' + else: + match = re.match(r'^(alpha|beta|rc)(\d*)$', release_type) + if match is None: + return 'unknown' + + if len(version_info) == 2: + fmt = '{version}{release_type}' + elif len(version_info) == 4: + fmt = '{version}{release_type}-{commits}' + + return fmt.format(**version_data) + + version_file = 'bpython/_version.py' version = 'unknown' @@ -46,13 +93,7 @@ stdout = stdout.decode('ascii') if proc.returncode == 0: - version_split = stdout.split('-') - if len(version_split) == 4: - # format: version-release-commits-hash - version = '-'.join((version_split[0], version_split[2])) - elif len(version_split) == 2: - # format: version-release - version = version_split[0] + version = git_describe_to_python_version(stdout) except OSError: pass From e18bab3b9d270a946d6eb1daf00cca0b6b16151c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Feb 2015 21:12:30 +0100 Subject: [PATCH 0460/1650] Simple test for GlobalCompletion Signed-off-by: Sebastian Ramacher --- bpython/test/test_autocomplete.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 6c0b39092..483673166 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -256,3 +256,17 @@ def test_completions_starting_with_different_cases(self): 2, ' a', 'class Foo:\n a', ['adsf'], [Comp('Abc', 'bc'), Comp('ade', 'de')]) self.assertSetEqual(matches, set(['ade'])) + + +class TestGlobalCompletion(unittest.TestCase): + + def setUp(self): + self.com = autocomplete.GlobalCompletion() + + def test_function(self): + def function(): + pass + + self.assertEqual(self.com.matches(8, 'function', + locals_={'function': function}), + set(('function(', ))) From aaaf99b667c12df75cecd0371bce96886ae456a5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Feb 2015 21:12:47 +0100 Subject: [PATCH 0461/1650] Test methods Signed-off-by: Sebastian Ramacher --- bpython/test/test_inspection.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 7fca0eec8..35eea43be 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -42,6 +42,10 @@ class Noncallable(object): def spam(): pass + class CallableMethod(object): + def method(self): + pass + self.assertTrue(inspection.is_callable(spam)) self.assertTrue(inspection.is_callable(Callable)) self.assertTrue(inspection.is_callable(Callable())) @@ -50,6 +54,7 @@ def spam(): self.assertFalse(inspection.is_callable(Noncallable())) self.assertFalse(inspection.is_callable(OldNoncallable())) self.assertFalse(inspection.is_callable(None)) + self.assertTrue(inspection.is_callable(CallableMethod().method)) def test_parsekeywordpairs(self): # See issue #109 From 0748500169059cbf46c31e12f26f0dc2d7a72fbe Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Feb 2015 21:13:14 +0100 Subject: [PATCH 0462/1650] Failing test for #479 Signed-off-by: Sebastian Ramacher --- bpython/test/test_repl.py | 61 ++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 14699a0c2..cab826aff 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -129,8 +129,14 @@ def setUp(self): self.repl.push("def spam(a, b, c):\n", False) self.repl.push(" pass\n", False) self.repl.push("\n", False) + self.repl.push("class Spam(object):\n", False) + self.repl.push(" def spam(self, a, b, c):\n", False) + self.repl.push(" pass\n", False) + self.repl.push("\n", False) + self.repl.push("o = Spam()\n", False) + self.repl.push("\n", False) - def setInputLine(self, line): + def set_input_line(self, line): """Set current input line of the test REPL.""" self.repl.current_line = line self.repl.cursor_offset = len(line) @@ -139,53 +145,62 @@ def test_func_name(self): for (line, expected_name) in [("spam(", "spam"), ("spam(map([]", "map"), ("spam((), ", "spam")]: - self.setInputLine(line) + self.set_input_line(line) + self.assertTrue(self.repl.get_args()) + self.assertEqual(self.repl.current_func.__name__, expected_name) + + def test_func_name_method_issue_479(self): + for (line, expected_name) in [("o.spam(", "spam"), + ("o.spam(map([]", "map"), + ("o.spam((), ", "spam")]: + self.set_input_line(line) self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.current_func.__name__, expected_name) + def test_syntax_error_parens(self): for line in ["spam(]", "spam([)", "spam())"]: - self.setInputLine(line) + self.set_input_line(line) # Should not explode self.repl.get_args() def test_kw_arg_position(self): - self.setInputLine("spam(a=0") + self.set_input_line("spam(a=0") self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.argspec[3], "a") - self.setInputLine("spam(1, b=1") + self.set_input_line("spam(1, b=1") self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.argspec[3], "b") - self.setInputLine("spam(1, c=2") + self.set_input_line("spam(1, c=2") self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.argspec[3], "c") def test_lambda_position(self): - self.setInputLine("spam(lambda a, b: 1, ") + self.set_input_line("spam(lambda a, b: 1, ") self.assertTrue(self.repl.get_args()) self.assertTrue(self.repl.argspec) # Argument position self.assertEqual(self.repl.argspec[3], 1) def test_issue127(self): - self.setInputLine("x=range(") + self.set_input_line("x=range(") self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.current_func.__name__, "range") - self.setInputLine("{x:range(") + self.set_input_line("{x:range(") self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.current_func.__name__, "range") - self.setInputLine("foo(1, 2, x,range(") + self.set_input_line("foo(1, 2, x,range(") self.assertEqual(self.repl.current_func.__name__, "range") - self.setInputLine("(x,range(") + self.set_input_line("(x,range(") self.assertEqual(self.repl.current_func.__name__, "range") def test_nonexistent_name(self): - self.setInputLine("spamspamspam(") + self.set_input_line("spamspamspam(") self.assertFalse(self.repl.get_args()) @@ -235,7 +250,7 @@ def test_current_line(self): class TestRepl(unittest.TestCase): - def setInputLine(self, line): + def set_input_line(self, line): """Set current input line of the test REPL.""" self.repl.current_line = line self.repl.cursor_offset = len(line) @@ -244,12 +259,12 @@ def setUp(self): self.repl = FakeRepl() def test_current_string(self): - self.setInputLine('a = "2"') + self.set_input_line('a = "2"') # TODO factor cpos out of repl.Repl self.repl.cpos = 0 self.assertEqual(self.repl.current_string(), '"2"') - self.setInputLine('a = "2" + 2') + self.set_input_line('a = "2" + 2') self.assertEqual(self.repl.current_string(), '') def test_push(self): @@ -261,7 +276,7 @@ def test_push(self): # 1. Global tests def test_simple_global_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) - self.setInputLine("d") + self.set_input_line("d") self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) @@ -272,7 +287,7 @@ def test_simple_global_complete(self): @unittest.skip("disabled while non-simple completion is disabled") def test_substring_global_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SUBSTRING}) - self.setInputLine("time") + self.set_input_line("time") self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.completer, 'matches')) @@ -282,7 +297,7 @@ def test_substring_global_complete(self): @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_global_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.FUZZY}) - self.setInputLine("doc") + self.set_input_line("doc") self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.completer, 'matches')) @@ -292,7 +307,7 @@ def test_fuzzy_global_complete(self): # 2. Attribute tests def test_simple_attribute_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) - self.setInputLine("Foo.b") + self.set_input_line("Foo.b") code = "class Foo():\n\tdef bar(self):\n\t\tpass\n" for line in code.split("\n"): @@ -305,7 +320,7 @@ def test_simple_attribute_complete(self): @unittest.skip("disabled while non-simple completion is disabled") def test_substring_attribute_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SUBSTRING}) - self.setInputLine("Foo.az") + self.set_input_line("Foo.az") code = "class Foo():\n\tdef baz(self):\n\t\tpass\n" for line in code.split("\n"): @@ -318,7 +333,7 @@ def test_substring_attribute_complete(self): @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_attribute_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.FUZZY}) - self.setInputLine("Foo.br") + self.set_input_line("Foo.br") code = "class Foo():\n\tdef bar(self):\n\t\tpass\n" for line in code.split("\n"): @@ -331,7 +346,7 @@ def test_fuzzy_attribute_complete(self): # 3. Edge Cases def test_updating_namespace_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) - self.setInputLine("foo") + self.set_input_line("foo") self.repl.push("foobar = 2") self.assertTrue(self.repl.complete()) @@ -340,7 +355,7 @@ def test_updating_namespace_complete(self): def test_file_should_not_appear_in_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) - self.setInputLine("_") + self.set_input_line("_") self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertNotIn('__file__', self.repl.matches_iter.matches) From 07e18d714039e9c4b698878a6e1354b6e88a62c8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Feb 2015 21:28:18 +0100 Subject: [PATCH 0463/1650] Use __self__ instead of im_self for Python 3 compatibility (fixes #479) Signed-off-by: Sebastian Ramacher --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 77fa6fbf6..0bc48be67 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -235,7 +235,7 @@ def getargspec(func, f): func_name = None try: - is_bound_method = ((inspect.ismethod(f) and f.im_self is not None) + is_bound_method = ((inspect.ismethod(f) and f.__self__ is not None) or (func_name == '__init__' and not func.endswith('.__init__'))) except: From 7594078950eb97bb022755ae9661e9b8eb978e68 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Feb 2015 21:50:18 +0100 Subject: [PATCH 0464/1650] PEP-8 Signed-off-by: Sebastian Ramacher --- bpython/test/test_autocomplete.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 39ced1322..390b73389 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -284,7 +284,10 @@ def func(apple, apricot, banana, carrot): argspec = list(inspect.getargspec(func)) argspec = ["func", argspec, False] - com=autocomplete.ParameterNameCompletion() - self.assertSetEqual(com.matches(1, "a", argspec=argspec), set(['apple=', 'apricot='])) - self.assertSetEqual(com.matches(2, "ba", argspec=argspec), set(['banana='])) - self.assertSetEqual(com.matches(3, "car", argspec=argspec), set(['carrot='])) + com = autocomplete.ParameterNameCompletion() + self.assertSetEqual(com.matches(1, "a", argspec=argspec), + set(['apple=', 'apricot='])) + self.assertSetEqual(com.matches(2, "ba", argspec=argspec), + set(['banana='])) + self.assertSetEqual(com.matches(3, "car", argspec=argspec), + set(['carrot='])) From 4cfbb76d1355641071f19800c66a2b56ab0f1203 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Feb 2015 21:57:58 +0100 Subject: [PATCH 0465/1650] Update translations Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 3 +- bpython/translations/bpython.pot | 118 +++++++++--------- .../translations/de/LC_MESSAGES/bpython.po | 116 ++++++++--------- .../translations/es_ES/LC_MESSAGES/bpython.po | 116 ++++++++--------- .../translations/fr_FR/LC_MESSAGES/bpython.po | 118 +++++++++--------- .../translations/it_IT/LC_MESSAGES/bpython.po | 116 ++++++++--------- .../translations/nl_NL/LC_MESSAGES/bpython.po | 116 ++++++++--------- 7 files changed, 351 insertions(+), 352 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index ba6ea2b5f..b80fd5b65 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -35,8 +35,7 @@ def main(args=None, locals_=None, banner=None): Option('--log', '-L', action='count', help=_("log debug messages to bpython.log")), Option('--paste', '-p', action='store_true', - help=_("start by pasting lines of a file into session" - "typed")), + help=_("start by pasting lines of a file into session")), ])) if options.log is None: options.log = 0 diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index c581cf0be..00b44b691 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.13-494\n" +"Project-Id-Version: bpython 0.13-641\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-02-02 17:34+0100\n" +"POT-Creation-Date: 2015-02-16 21:56+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,34 +17,34 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: bpython/args.py:57 +#: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:67 +#: bpython/args.py:69 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:69 +#: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:72 +#: bpython/args.py:74 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:74 +#: bpython/args.py:76 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "y" msgstr "" -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "yes" msgstr "" @@ -68,222 +68,222 @@ msgstr "" msgid "Show Source" msgstr "" -#: bpython/curtsies.py:31 +#: bpython/curtsies.py:36 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:33 -msgid "enter lines of file as though interactively typed" +#: bpython/curtsies.py:38 +msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:216 +#: bpython/history.py:223 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:513 +#: bpython/repl.py:537 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:518 +#: bpython/repl.py:542 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:523 +#: bpython/repl.py:547 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:525 +#: bpython/repl.py:549 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:652 +#: bpython/repl.py:681 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:696 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:671 +#: bpython/repl.py:700 msgid "overwrite" msgstr "" -#: bpython/repl.py:673 +#: bpython/repl.py:702 msgid "append" msgstr "" -#: bpython/repl.py:685 bpython/repl.py:1027 +#: bpython/repl.py:714 bpython/repl.py:1075 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:688 +#: bpython/repl.py:717 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:694 +#: bpython/repl.py:723 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:701 +#: bpython/repl.py:730 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:703 +#: bpython/repl.py:732 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:712 +#: bpython/repl.py:741 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:713 +#: bpython/repl.py:742 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:720 +#: bpython/repl.py:749 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:739 bpython/repl.py:767 +#: bpython/repl.py:770 bpython/repl.py:799 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:744 +#: bpython/repl.py:775 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:760 +#: bpython/repl.py:792 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:779 +#: bpython/repl.py:811 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:782 +#: bpython/repl.py:814 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:789 +#: bpython/repl.py:821 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "" -#: bpython/repl.py:793 +#: bpython/repl.py:825 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:800 +#: bpython/repl.py:833 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:806 +#: bpython/repl.py:839 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:839 +#: bpython/repl.py:872 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:846 bpython/repl.py:850 +#: bpython/repl.py:879 bpython/repl.py:883 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:853 +#: bpython/repl.py:886 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1017 +#: bpython/repl.py:1062 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1034 +#: bpython/repl.py:1082 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1037 +#: bpython/repl.py:1085 msgid "Error editing config file." msgstr "" -#: bpython/urwid.py:617 +#: bpython/urwid.py:619 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -#: bpython/urwid.py:1126 +#: bpython/urwid.py:1128 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1131 +#: bpython/urwid.py:1133 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1135 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:286 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:287 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:472 +#: bpython/curtsiesfrontend/repl.py:526 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:489 +#: bpython/curtsiesfrontend/repl.py:543 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:745 +#: bpython/curtsiesfrontend/repl.py:814 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:751 +#: bpython/curtsiesfrontend/repl.py:820 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:756 +#: bpython/curtsiesfrontend/repl.py:825 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:761 +#: bpython/curtsiesfrontend/repl.py:830 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 3d81ab2bc..5afe91d62 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-02 17:34+0100\n" +"POT-Creation-Date: 2015-02-16 21:56+0100\n" "PO-Revision-Date: 2015-02-05 14:54+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: de \n" @@ -17,34 +17,34 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: bpython/args.py:57 +#: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:67 +#: bpython/args.py:69 msgid "Use CONFIG instead of default config file." msgstr "Verwende CONFIG antatt der standardmäßigen Konfigurationsdatei." -#: bpython/args.py:69 +#: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "Verbleibe in bpython nach dem Ausführen der Datei." -#: bpython/args.py:72 +#: bpython/args.py:74 msgid "Don't flush the output to stdout." msgstr "Gib Ausgabe beim Beenden nicht ernaut auf stdout aus." -#: bpython/args.py:74 +#: bpython/args.py:76 msgid "Print version and exit." msgstr "Zeige Versionsinformationen an und beende." -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "y" msgstr "j" -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "yes" msgstr "ja" @@ -68,230 +68,230 @@ msgstr "" msgid "Show Source" msgstr "Quellcode anzeigen" -#: bpython/curtsies.py:31 +#: bpython/curtsies.py:36 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:33 -msgid "enter lines of file as though interactively typed" +#: bpython/curtsies.py:38 +msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:216 +#: bpython/history.py:223 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" -#: bpython/repl.py:513 +#: bpython/repl.py:537 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:518 +#: bpython/repl.py:542 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" -#: bpython/repl.py:523 +#: bpython/repl.py:547 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/repl.py:525 +#: bpython/repl.py:549 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" -#: bpython/repl.py:652 +#: bpython/repl.py:681 msgid "Save to file (Esc to cancel): " msgstr "In Datei speichern (Esc um abzubrechen): " -#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 msgid "Save cancelled." msgstr "Speichern abgebrochen." -#: bpython/repl.py:667 +#: bpython/repl.py:696 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" -#: bpython/repl.py:671 +#: bpython/repl.py:700 msgid "overwrite" msgstr "überschreiben" -#: bpython/repl.py:673 +#: bpython/repl.py:702 msgid "append" msgstr "anhängen" -#: bpython/repl.py:685 bpython/repl.py:1027 +#: bpython/repl.py:714 bpython/repl.py:1075 #, python-format msgid "Error writing file '%s': %s" msgstr "Fehler beim Schreiben in Datei '%s': %s" -#: bpython/repl.py:688 +#: bpython/repl.py:717 #, python-format msgid "Saved to %s." msgstr "Nach %s gespeichert." -#: bpython/repl.py:694 +#: bpython/repl.py:723 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/repl.py:701 +#: bpython/repl.py:730 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/repl.py:703 +#: bpython/repl.py:732 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:712 +#: bpython/repl.py:741 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:713 +#: bpython/repl.py:742 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:720 +#: bpython/repl.py:749 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:739 bpython/repl.py:767 +#: bpython/repl.py:770 bpython/repl.py:799 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." -#: bpython/repl.py:744 +#: bpython/repl.py:775 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:760 +#: bpython/repl.py:792 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:779 +#: bpython/repl.py:811 msgid "Upload failed: Helper program not found." msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht gefunden werden." -#: bpython/repl.py:782 +#: bpython/repl.py:814 msgid "Upload failed: Helper program could not be run." msgstr "" "Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht ausgeführt " "werden." -#: bpython/repl.py:789 +#: bpython/repl.py:821 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm beendete mit Status %d." -#: bpython/repl.py:793 +#: bpython/repl.py:825 msgid "Upload failed: No output from helper program." msgstr "Hochladen ist fehlgeschlagen: Keine Ausgabe von Hilfsprogramm vorhanden." -#: bpython/repl.py:800 +#: bpython/repl.py:833 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" "Hochladen ist fehlgeschlagen: Konte Ausgabe von Hilfsprogramm nicht " "verarbeiten." -#: bpython/repl.py:806 +#: bpython/repl.py:839 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:839 +#: bpython/repl.py:872 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:846 bpython/repl.py:850 +#: bpython/repl.py:879 bpython/repl.py:883 msgid "Undo canceled" msgstr "Rückgängigmachen abgebrochen" -#: bpython/repl.py:853 +#: bpython/repl.py:886 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1017 +#: bpython/repl.py:1062 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" "Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " "werden? (j/N)" -#: bpython/repl.py:1034 +#: bpython/repl.py:1082 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" "bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " "Änderungen übernommen werden." -#: bpython/repl.py:1037 +#: bpython/repl.py:1085 msgid "Error editing config file." msgstr "Fehler beim Bearbeiten der Konfigurationsdatei." -#: bpython/urwid.py:617 +#: bpython/urwid.py:619 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -#: bpython/urwid.py:1126 +#: bpython/urwid.py:1128 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1131 +#: bpython/urwid.py:1133 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1135 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:286 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:287 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#: bpython/curtsiesfrontend/repl.py:472 +#: bpython/curtsiesfrontend/repl.py:526 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" -#: bpython/curtsiesfrontend/repl.py:489 +#: bpython/curtsiesfrontend/repl.py:543 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:745 +#: bpython/curtsiesfrontend/repl.py:814 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:751 +#: bpython/curtsiesfrontend/repl.py:820 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:756 +#: bpython/curtsiesfrontend/repl.py:825 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:761 +#: bpython/curtsiesfrontend/repl.py:830 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 01aee8886..34604d59f 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-02 17:34+0100\n" +"POT-Creation-Date: 2015-02-16 21:56+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" @@ -17,34 +17,34 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: bpython/args.py:57 +#: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:67 +#: bpython/args.py:69 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:69 +#: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:72 +#: bpython/args.py:74 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:74 +#: bpython/args.py:76 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "y" msgstr "s" -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "yes" msgstr "si" @@ -68,224 +68,224 @@ msgstr "" msgid "Show Source" msgstr "" -#: bpython/curtsies.py:31 +#: bpython/curtsies.py:36 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:33 -msgid "enter lines of file as though interactively typed" +#: bpython/curtsies.py:38 +msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:216 +#: bpython/history.py:223 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:513 +#: bpython/repl.py:537 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:518 +#: bpython/repl.py:542 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:523 +#: bpython/repl.py:547 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:525 +#: bpython/repl.py:549 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:652 +#: bpython/repl.py:681 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:696 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:671 +#: bpython/repl.py:700 msgid "overwrite" msgstr "" -#: bpython/repl.py:673 +#: bpython/repl.py:702 msgid "append" msgstr "" -#: bpython/repl.py:685 bpython/repl.py:1027 +#: bpython/repl.py:714 bpython/repl.py:1075 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:688 +#: bpython/repl.py:717 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:694 +#: bpython/repl.py:723 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:701 +#: bpython/repl.py:730 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:703 +#: bpython/repl.py:732 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:712 +#: bpython/repl.py:741 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:713 +#: bpython/repl.py:742 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:720 +#: bpython/repl.py:749 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:739 bpython/repl.py:767 +#: bpython/repl.py:770 bpython/repl.py:799 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:744 +#: bpython/repl.py:775 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:760 +#: bpython/repl.py:792 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:779 +#: bpython/repl.py:811 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:782 +#: bpython/repl.py:814 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:789 +#: bpython/repl.py:821 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "" -#: bpython/repl.py:793 +#: bpython/repl.py:825 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:800 +#: bpython/repl.py:833 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:806 +#: bpython/repl.py:839 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:839 +#: bpython/repl.py:872 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:846 bpython/repl.py:850 +#: bpython/repl.py:879 bpython/repl.py:883 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:853 +#: bpython/repl.py:886 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1017 +#: bpython/repl.py:1062 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1034 +#: bpython/repl.py:1082 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1037 +#: bpython/repl.py:1085 msgid "Error editing config file." msgstr "" -#: bpython/urwid.py:617 +#: bpython/urwid.py:619 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra el " "código fuente" -#: bpython/urwid.py:1126 +#: bpython/urwid.py:1128 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1131 +#: bpython/urwid.py:1133 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1135 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:286 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:287 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:472 +#: bpython/curtsiesfrontend/repl.py:526 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:489 +#: bpython/curtsiesfrontend/repl.py:543 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:745 +#: bpython/curtsiesfrontend/repl.py:814 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:751 +#: bpython/curtsiesfrontend/repl.py:820 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:756 +#: bpython/curtsiesfrontend/repl.py:825 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:761 +#: bpython/curtsiesfrontend/repl.py:830 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index 92f31ea59..1bb4aaff5 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-02 17:34+0100\n" +"POT-Creation-Date: 2015-02-16 21:56+0100\n" "PO-Revision-Date: 2015-02-05 14:54+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" @@ -16,7 +16,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: bpython/args.py:57 +#: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " @@ -26,29 +26,29 @@ msgstr "" "NOTE: Si bpython ne reconnaît pas un des arguments fournis, " "l'interpréteur Python classique sera lancé" -#: bpython/args.py:67 +#: bpython/args.py:69 msgid "Use CONFIG instead of default config file." msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." -#: bpython/args.py:69 +#: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" "Aller dans le shell bpython après l'exécution du fichier au lieu de " "quitter." -#: bpython/args.py:72 +#: bpython/args.py:74 msgid "Don't flush the output to stdout." msgstr "Ne pas purger la sortie vers stdout." -#: bpython/args.py:74 +#: bpython/args.py:76 msgid "Print version and exit." msgstr "Afficher la version et quitter." -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "y" msgstr "o" -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "yes" msgstr "oui" @@ -72,186 +72,186 @@ msgstr "" msgid "Show Source" msgstr "Montrer le code source" -#: bpython/curtsies.py:31 +#: bpython/curtsies.py:36 msgid "log debug messages to bpython.log" msgstr "logger les messages de debug dans bpython.log" -#: bpython/curtsies.py:33 -msgid "enter lines of file as though interactively typed" -msgstr "saisir les lignes du fichier interactivement" +#: bpython/curtsies.py:38 +msgid "start by pasting lines of a file into session" +msgstr "" -#: bpython/history.py:216 +#: bpython/history.py:223 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" -#: bpython/repl.py:513 +#: bpython/repl.py:537 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:518 +#: bpython/repl.py:542 #, python-format msgid "Cannot get source: %s" msgstr "Impossible de récupérer le source: %s" -#: bpython/repl.py:523 +#: bpython/repl.py:547 #, python-format msgid "Cannot access source of %r" msgstr "Impossible d'accéder au source de %r" -#: bpython/repl.py:525 +#: bpython/repl.py:549 #, python-format msgid "No source code found for %s" msgstr "Pas de code source trouvé pour %s" -#: bpython/repl.py:652 +#: bpython/repl.py:681 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:696 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:671 +#: bpython/repl.py:700 msgid "overwrite" msgstr "" -#: bpython/repl.py:673 +#: bpython/repl.py:702 msgid "append" msgstr "" -#: bpython/repl.py:685 bpython/repl.py:1027 +#: bpython/repl.py:714 bpython/repl.py:1075 #, python-format msgid "Error writing file '%s': %s" msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" -#: bpython/repl.py:688 +#: bpython/repl.py:717 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:694 +#: bpython/repl.py:723 msgid "No clipboard available." msgstr "Pas de presse-papier disponible." -#: bpython/repl.py:701 +#: bpython/repl.py:730 msgid "Could not copy to clipboard." msgstr "Impossible de copier vers le presse-papier." -#: bpython/repl.py:703 +#: bpython/repl.py:732 msgid "Copied content to clipboard." msgstr "Contenu copié vers le presse-papier." -#: bpython/repl.py:712 +#: bpython/repl.py:741 msgid "Pastebin buffer? (y/N) " msgstr "Tampon Pastebin ? (o/N) " -#: bpython/repl.py:713 +#: bpython/repl.py:742 msgid "Pastebin aborted." msgstr "Pastebin abandonné." -#: bpython/repl.py:720 +#: bpython/repl.py:749 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" -#: bpython/repl.py:739 bpython/repl.py:767 +#: bpython/repl.py:770 bpython/repl.py:799 msgid "Posting data to pastebin..." msgstr "Envoi des donnés à pastebin..." -#: bpython/repl.py:744 +#: bpython/repl.py:775 #, python-format msgid "Upload failed: %s" msgstr "Echec du téléchargement: %s" -#: bpython/repl.py:760 +#: bpython/repl.py:792 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "URL Pastebin: %s - URL de suppression: %s" -#: bpython/repl.py:779 +#: bpython/repl.py:811 msgid "Upload failed: Helper program not found." msgstr "Echec de l'upload: programme externe non trouvé." -#: bpython/repl.py:782 +#: bpython/repl.py:814 msgid "Upload failed: Helper program could not be run." msgstr "Echec de l'upload: impossible de lancer le programme externe." -#: bpython/repl.py:789 +#: bpython/repl.py:821 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "" "Echec de l'upload: le programme externe a renvoyé un statut de sortie " "différent de zéro %d." -#: bpython/repl.py:793 +#: bpython/repl.py:825 msgid "Upload failed: No output from helper program." msgstr "Echec de l'upload: pas de sortie du programme externe." -#: bpython/repl.py:800 +#: bpython/repl.py:833 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" "Echec de l'upload: la sortie du programme externe ne correspond pas à une" " URL." -#: bpython/repl.py:806 +#: bpython/repl.py:839 #, python-format msgid "Pastebin URL: %s" msgstr "URL Pastebin: %s" -#: bpython/repl.py:839 +#: bpython/repl.py:872 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:846 bpython/repl.py:850 +#: bpython/repl.py:879 bpython/repl.py:883 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:853 +#: bpython/repl.py:886 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1017 +#: bpython/repl.py:1062 msgid "Config file does not exist - create new from default? (y/N)" msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" -#: bpython/repl.py:1034 +#: bpython/repl.py:1082 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1037 +#: bpython/repl.py:1085 msgid "Error editing config file." msgstr "" -#: bpython/urwid.py:617 +#: bpython/urwid.py:619 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> " "Montrer Source " -#: bpython/urwid.py:1126 +#: bpython/urwid.py:1128 msgid "Run twisted reactor." msgstr "Lancer le reactor twisted." -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." -#: bpython/urwid.py:1131 +#: bpython/urwid.py:1133 msgid "List available reactors for -r." msgstr "Lister les reactors disponibles pour -r." -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1135 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." @@ -259,43 +259,43 @@ msgstr "" "plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" " "pour donner plus d'options au plugin." -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "Port pour lancer un server eval (force Twisted)." -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:286 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:287 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." -#: bpython/curtsiesfrontend/repl.py:472 +#: bpython/curtsiesfrontend/repl.py:526 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" -#: bpython/curtsiesfrontend/repl.py:489 +#: bpython/curtsiesfrontend/repl.py:543 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:745 +#: bpython/curtsiesfrontend/repl.py:814 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:751 +#: bpython/curtsiesfrontend/repl.py:820 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:756 +#: bpython/curtsiesfrontend/repl.py:825 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:761 +#: bpython/curtsiesfrontend/repl.py:830 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index 92a49c8ac..08446d970 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-02 17:34+0100\n" +"POT-Creation-Date: 2015-02-16 21:56+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: Michele Orrù\n" @@ -17,34 +17,34 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: bpython/args.py:57 +#: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:67 +#: bpython/args.py:69 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:69 +#: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:72 +#: bpython/args.py:74 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:74 +#: bpython/args.py:76 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "y" msgstr "s" -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "yes" msgstr "si" @@ -68,222 +68,222 @@ msgstr "" msgid "Show Source" msgstr "" -#: bpython/curtsies.py:31 +#: bpython/curtsies.py:36 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:33 -msgid "enter lines of file as though interactively typed" +#: bpython/curtsies.py:38 +msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:216 +#: bpython/history.py:223 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:513 +#: bpython/repl.py:537 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:518 +#: bpython/repl.py:542 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:523 +#: bpython/repl.py:547 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:525 +#: bpython/repl.py:549 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:652 +#: bpython/repl.py:681 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:696 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:671 +#: bpython/repl.py:700 msgid "overwrite" msgstr "" -#: bpython/repl.py:673 +#: bpython/repl.py:702 msgid "append" msgstr "" -#: bpython/repl.py:685 bpython/repl.py:1027 +#: bpython/repl.py:714 bpython/repl.py:1075 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:688 +#: bpython/repl.py:717 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:694 +#: bpython/repl.py:723 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:701 +#: bpython/repl.py:730 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:703 +#: bpython/repl.py:732 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:712 +#: bpython/repl.py:741 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:713 +#: bpython/repl.py:742 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:720 +#: bpython/repl.py:749 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:739 bpython/repl.py:767 +#: bpython/repl.py:770 bpython/repl.py:799 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:744 +#: bpython/repl.py:775 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:760 +#: bpython/repl.py:792 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:779 +#: bpython/repl.py:811 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:782 +#: bpython/repl.py:814 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:789 +#: bpython/repl.py:821 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "" -#: bpython/repl.py:793 +#: bpython/repl.py:825 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:800 +#: bpython/repl.py:833 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:806 +#: bpython/repl.py:839 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:839 +#: bpython/repl.py:872 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:846 bpython/repl.py:850 +#: bpython/repl.py:879 bpython/repl.py:883 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:853 +#: bpython/repl.py:886 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1017 +#: bpython/repl.py:1062 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1034 +#: bpython/repl.py:1082 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1037 +#: bpython/repl.py:1085 msgid "Error editing config file." msgstr "" -#: bpython/urwid.py:617 +#: bpython/urwid.py:619 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" -#: bpython/urwid.py:1126 +#: bpython/urwid.py:1128 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1131 +#: bpython/urwid.py:1133 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1135 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:286 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:287 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:472 +#: bpython/curtsiesfrontend/repl.py:526 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:489 +#: bpython/curtsiesfrontend/repl.py:543 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:745 +#: bpython/curtsiesfrontend/repl.py:814 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:751 +#: bpython/curtsiesfrontend/repl.py:820 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:756 +#: bpython/curtsiesfrontend/repl.py:825 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:761 +#: bpython/curtsiesfrontend/repl.py:830 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index 2bc24908c..2da674b5e 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-02 17:34+0100\n" +"POT-Creation-Date: 2015-02-16 21:56+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" @@ -17,34 +17,34 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: bpython/args.py:57 +#: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:67 +#: bpython/args.py:69 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:69 +#: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:72 +#: bpython/args.py:74 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:74 +#: bpython/args.py:76 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "y" msgstr "j" -#: bpython/cli.py:318 bpython/urwid.py:555 +#: bpython/cli.py:318 bpython/urwid.py:557 msgid "yes" msgstr "ja" @@ -68,222 +68,222 @@ msgstr "" msgid "Show Source" msgstr "" -#: bpython/curtsies.py:31 +#: bpython/curtsies.py:36 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:33 -msgid "enter lines of file as though interactively typed" +#: bpython/curtsies.py:38 +msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:216 +#: bpython/history.py:223 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:513 +#: bpython/repl.py:537 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:518 +#: bpython/repl.py:542 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:523 +#: bpython/repl.py:547 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:525 +#: bpython/repl.py:549 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:652 +#: bpython/repl.py:681 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:654 bpython/repl.py:657 bpython/repl.py:676 +#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:696 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:671 +#: bpython/repl.py:700 msgid "overwrite" msgstr "" -#: bpython/repl.py:673 +#: bpython/repl.py:702 msgid "append" msgstr "" -#: bpython/repl.py:685 bpython/repl.py:1027 +#: bpython/repl.py:714 bpython/repl.py:1075 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:688 +#: bpython/repl.py:717 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:694 +#: bpython/repl.py:723 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:701 +#: bpython/repl.py:730 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:703 +#: bpython/repl.py:732 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:712 +#: bpython/repl.py:741 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:713 +#: bpython/repl.py:742 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:720 +#: bpython/repl.py:749 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:739 bpython/repl.py:767 +#: bpython/repl.py:770 bpython/repl.py:799 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:744 +#: bpython/repl.py:775 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:760 +#: bpython/repl.py:792 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:779 +#: bpython/repl.py:811 msgid "Upload failed: Helper program not found." msgstr "" -#: bpython/repl.py:782 +#: bpython/repl.py:814 msgid "Upload failed: Helper program could not be run." msgstr "" -#: bpython/repl.py:789 +#: bpython/repl.py:821 #, python-format msgid "Upload failed: Helper program returned non-zero exit status %d." msgstr "" -#: bpython/repl.py:793 +#: bpython/repl.py:825 msgid "Upload failed: No output from helper program." msgstr "" -#: bpython/repl.py:800 +#: bpython/repl.py:833 msgid "Upload failed: Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:806 +#: bpython/repl.py:839 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:839 +#: bpython/repl.py:872 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:846 bpython/repl.py:850 +#: bpython/repl.py:879 bpython/repl.py:883 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:853 +#: bpython/repl.py:886 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1017 +#: bpython/repl.py:1062 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1034 +#: bpython/repl.py:1082 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1037 +#: bpython/repl.py:1085 msgid "Error editing config file." msgstr "" -#: bpython/urwid.py:617 +#: bpython/urwid.py:619 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" -#: bpython/urwid.py:1126 +#: bpython/urwid.py:1128 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1131 +#: bpython/urwid.py:1133 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1135 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:286 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:259 +#: bpython/curtsiesfrontend/repl.py:287 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:472 +#: bpython/curtsiesfrontend/repl.py:526 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:489 +#: bpython/curtsiesfrontend/repl.py:543 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:745 +#: bpython/curtsiesfrontend/repl.py:814 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:751 +#: bpython/curtsiesfrontend/repl.py:820 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:756 +#: bpython/curtsiesfrontend/repl.py:825 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:761 +#: bpython/curtsiesfrontend/repl.py:830 msgid "Auto-reloading not available because watchdog not installed." msgstr "" From cbb4fac593c57eb68cd04ae9e83e889679dca076 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Feb 2015 22:05:29 +0100 Subject: [PATCH 0466/1650] Document --type / -t -> --paste / -p change Signed-off-by: Sebastian Ramacher --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e7402b5ae..94feb6505 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -67,6 +67,8 @@ Fixes: * Fixed various Unicode issues in curtsies. * Fixed and re-enabled dictionary key completion in curtsies. +The commandline option --type / -t has been renamed to --paste / -p. + Python 2.6, 2.7, 3.3 and newer are supported. Support for 2.5 has been dropped. Furthermore, it is no longer necessary to run 2to3 on the source code. From a5205b749d904b1c9ffa62241174449d75445009 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Feb 2015 23:46:35 +0100 Subject: [PATCH 0467/1650] Start development of 0.15 --- CHANGELOG | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 94feb6505..2f19c242b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,15 @@ Changelog ========= +0.15 +---- + +New features: + + +Fixes: + + 0.14 ---- From 8a70bcbe1ecc23733879039ca821ce3937a5278d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 17 Feb 2015 14:43:33 +0100 Subject: [PATCH 0468/1650] Fix a changelog entry Signed-off-by: Sebastian Ramacher --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2f19c242b..5268145ff 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,7 +22,7 @@ This release contains major changes to the frontends: New features: * #194: Syntax-highlighted tracebacks. Thanks to Miriam Lauter. -* #234: Copy to system keyboard. +* #234: Copy to system clipboard. * #285: Re-evaluate session and reimport modules. * #313: Warn when undo may take cause extended delay, and prompt to undo multiple lines. From 7e4b9d43d5953ad75635479e0e52d1949a65e06c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 17 Feb 2015 17:32:59 +0100 Subject: [PATCH 0469/1650] Use assertRaises Signed-off-by: Sebastian Ramacher --- bpython/test/test_args.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 04bbd9d4d..62b49aab5 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -39,9 +39,5 @@ def test_exec_dunder_file(self): class TestParse(TestCase): def test_version(self): - try: + with self.assertRaises(SystemExit): args.parse(['--version']) - except SystemExit: - pass - else: - self.fail('Should have raise SystemExit') From 5a021f8c64306368322d24fd8610b12223c2ffd1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 17 Feb 2015 17:58:18 +0100 Subject: [PATCH 0470/1650] Turn ps1 and ps2 into unicode Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 43278fe0d..2fd783830 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -388,16 +388,23 @@ def __init__(self, interp, config): @property def ps1(self): try: - return str(sys.ps1) + if not py3: + return sys.ps1.decode(getpreferredencoding()) + else: + return sys.ps1 except AttributeError: - return '>>> ' + return u'>>> ' @property def ps2(self): try: - return str(sys.ps2) + if not py3: + return sys.ps2.decode(getpreferredencoding()) + else: + return sys.ps2 + except AttributeError: - return '... ' + return u'... ' def startup(self): """ From b230413d0e01dc2041d7405169eba841ff115997 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 17 Feb 2015 18:35:53 +0100 Subject: [PATCH 0471/1650] Unicode-ification of bpython.curtsiesfrontend Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/parse.py | 8 +- bpython/curtsiesfrontend/repl.py | 1 + bpython/curtsiesfrontend/replpainter.py | 1 + bpython/test/test_curtsies_painting.py | 378 ++++++++++++------------ 4 files changed, 196 insertions(+), 192 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index a09d17707..15a4c04d5 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -1,12 +1,14 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from functools import partial +import re from bpython.lazyre import LazyReCompile from curtsies.termformatconstants import FG_COLORS, BG_COLORS, colors from curtsies.formatstring import fmtstr, FmtStr -from functools import partial - -import re cnames = dict(zip('krgybmcwd', colors + ('default',))) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 54f83873c..95704fa34 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals import contextlib import errno diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 1b0db9343..02911e0d0 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals import logging import itertools diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 420a5230c..59c910d4f 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -82,7 +82,7 @@ def test_run_line(self): sys.stdout = self.repl.stdout [self.repl.add_normal_character(c) for c in '1 + 1'] self.repl.on_enter(insert_into_history=False) - screen = fsarray([u'>>> 1 + 1', '2', 'Welcome to']) + screen = fsarray(['>>> 1 + 1', '2', 'Welcome to']) self.assert_paint_ignoring_formatting(screen, (1, 1)) finally: sys.stdout = orig_stdout @@ -92,17 +92,17 @@ def test_completion(self): self.repl.current_line = 'se' self.cursor_offset = 2 if config.supports_box_chars(): - screen = [u'>>> se', - u'┌───────────────────────┐', - u'│ set( setattr( │', - u'└───────────────────────┘', - u'Welcome to bpython! Press f'] + screen = ['>>> se', + '┌───────────────────────┐', + '│ set( setattr( │', + '└───────────────────────┘', + 'Welcome to bpython! Press f'] else: - screen = [u'>>> se', - u'+-----------------------+', - u'| set( setattr( |', - u'+-----------------------+', - u'Welcome to bpython! Press f'] + screen = ['>>> se', + '+-----------------------+', + '| set( setattr( |', + '+-----------------------+', + 'Welcome to bpython! Press f'] self.assert_paint_ignoring_formatting(screen, (0, 4)) def test_argspec(self): @@ -111,7 +111,7 @@ def foo(x, y, z=10): pass argspec = inspection.getargspec('foo', foo) + [1] array = replpainter.formatted_argspec(argspec, 30, setup_config()) - screen = [bold(cyan(u'foo')) + cyan(':') + cyan(' ') + cyan('(') + + screen = [bold(cyan('foo')) + cyan(':') + cyan(' ') + cyan('(') + cyan('x') + yellow(',') + yellow(' ') + bold(cyan('y')) + yellow(',') + yellow(' ') + cyan('z') + yellow('=') + bold(cyan('10')) + yellow(')')] @@ -190,12 +190,12 @@ def setUp(self): def test_rewind(self): self.repl.current_line = '1 + 1' self.enter() - screen = [u'>>> 1 + 1', - u'2', - u'>>> '] + screen = ['>>> 1 + 1', + '2', + '>>> '] self.assert_paint_ignoring_formatting(screen, (2, 4)) self.repl.undo() - screen = [u'>>> '] + screen = ['>>> '] self.assert_paint_ignoring_formatting(screen, (0, 4)) def test_rewind_contiguity_loss(self): @@ -203,78 +203,78 @@ def test_rewind_contiguity_loss(self): self.enter('2 + 2') self.enter('def foo(x):') self.repl.current_line = ' return x + 1' - screen = [u'>>> 1 + 1', - u'2', - u'>>> 2 + 2', - u'4', - u'>>> def foo(x):', - u'... return x + 1'] + screen = ['>>> 1 + 1', + '2', + '>>> 2 + 2', + '4', + '>>> def foo(x):', + '... return x + 1'] self.assert_paint_ignoring_formatting(screen, (5, 8)) self.repl.scroll_offset = 1 self.assert_paint_ignoring_formatting(screen[1:], (4, 8)) self.undo() - screen = [u'2', - u'>>> 2 + 2', - u'4', - u'>>> '] + screen = ['2', + '>>> 2 + 2', + '4', + '>>> '] self.assert_paint_ignoring_formatting(screen, (3, 4)) self.undo() - screen = [u'2', - u'>>> '] + screen = ['2', + '>>> '] self.assert_paint_ignoring_formatting(screen, (1, 4)) self.undo() screen = [CONTIGUITY_BROKEN_MSG[:self.repl.width], - u'>>> ', - u'', - u'', - u'', - u' '] # TODO why is that there? Necessary? + '>>> ', + '', + '', + '', + ' '] # TODO why is that there? Necessary? self.assert_paint_ignoring_formatting(screen, (1, 4)) - screen = [u'>>> '] + screen = ['>>> '] self.assert_paint_ignoring_formatting(screen, (0, 4)) def test_inconsistent_history_doesnt_happen_if_onscreen(self): - self.enter("1 + 1") - screen = [u">>> 1 + 1", - u'2', - u'>>> '] + self.enter('1 + 1') + screen = ['>>> 1 + 1', + '2', + '>>> '] self.assert_paint_ignoring_formatting(screen, (2, 4)) self.enter("2 + 2") - screen = [u">>> 1 + 1", - u'2', - u'>>> 2 + 2', - u'4', - u'>>> '] + screen = ['>>> 1 + 1', + '2', + '>>> 2 + 2', + '4', + '>>> '] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.display_lines[0] = self.repl.display_lines[0] * 2 self.undo() - screen = [u">>> 1 + 1", - u'2', - u'>>> '] + screen = ['>>> 1 + 1', + '2', + '>>> '] self.assert_paint_ignoring_formatting(screen, (2, 4)) def test_rewind_inconsistent_history(self): - self.enter("1 + 1") - self.enter("2 + 2") - self.enter("3 + 3") - screen = [u">>> 1 + 1", - u'2', - u'>>> 2 + 2', - u'4', - u'>>> 3 + 3', - u'6', - u'>>> '] + self.enter('1 + 1') + self.enter('2 + 2') + self.enter('3 + 3') + screen = ['>>> 1 + 1', + '2', + '>>> 2 + 2', + '4', + '>>> 3 + 3', + '6', + '>>> '] self.assert_paint_ignoring_formatting(screen, (6, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[2:], (4, 4)) self.repl.display_lines[0] = self.repl.display_lines[0] * 2 self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], - u'>>> 2 + 2', - u'4', - u'>>> ', - u'', - u' '] + '>>> 2 + 2', + '4', + '>>> ', + '', + ' '] self.assert_paint_ignoring_formatting(screen, (3, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4)) @@ -283,37 +283,37 @@ def test_rewind_inconsistent_history(self): def test_rewind_inconsistent_history_more_lines_same_screen(self): self.repl.width = 60 sys.a = 5 - self.enter("import sys") - self.enter("for i in range(sys.a): print(sys.a)") + self.enter('import sys') + self.enter('for i in range(sys.a): print(sys.a)') self.enter() - self.enter("1 + 1") - self.enter("2 + 2") - screen = [u">>> import sys", - u">>> for i in range(sys.a): print(sys.a)", - u'... ', - u'5', - u'5', - u'5', - u'5', - u'5', - u'>>> 1 + 1', - u'2', - u'>>> 2 + 2', - u'4', - u'>>> '] + self.enter('1 + 1') + self.enter('2 + 2') + screen = ['>>> import sys', + '>>> for i in range(sys.a): print(sys.a)', + '... ', + '5', + '5', + '5', + '5', + '5', + '>>> 1 + 1', + '2', + '>>> 2 + 2', + '4', + '>>> '] self.assert_paint_ignoring_formatting(screen, (12, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) sys.a = 6 self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], - u'6', + '6', # everything will jump down a line - that's perfectly # reasonable - u'>>> 1 + 1', - u'2', - u'>>> ', - u' '] + '>>> 1 + 1', + '2', + '>>> ', + ' '] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:-1], (3, 4)) @@ -326,31 +326,31 @@ def test_rewind_inconsistent_history_more_lines_lower_screen(self): self.enter() self.enter("1 + 1") self.enter("2 + 2") - screen = [u">>> import sys", - u">>> for i in range(sys.a): print(sys.a)", - u'... ', - u'5', - u'5', - u'5', - u'5', - u'5', - u'>>> 1 + 1', - u'2', - u'>>> 2 + 2', - u'4', - u'>>> '] + screen = [">>> import sys", + ">>> for i in range(sys.a): print(sys.a)", + '... ', + '5', + '5', + '5', + '5', + '5', + '>>> 1 + 1', + '2', + '>>> 2 + 2', + '4', + '>>> '] self.assert_paint_ignoring_formatting(screen, (12, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) sys.a = 8 self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], - u'8', - u'8', - u'8', - u'>>> 1 + 1', - u'2', - u'>>> '] + '8', + '8', + '8', + '>>> 1 + 1', + '2', + '>>> '] self.assert_paint_ignoring_formatting(screen) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[-5:]) @@ -363,30 +363,30 @@ def test_rewind_inconsistent_history_more_lines_raise_screen(self): self.enter() self.enter("1 + 1") self.enter("2 + 2") - screen = [u">>> import sys", - u">>> for i in range(sys.a): print(sys.a)", - u'... ', - u'5', - u'5', - u'5', - u'5', - u'5', - u'>>> 1 + 1', - u'2', - u'>>> 2 + 2', - u'4', - u'>>> '] + screen = [">>> import sys", + ">>> for i in range(sys.a): print(sys.a)", + '... ', + '5', + '5', + '5', + '5', + '5', + '>>> 1 + 1', + '2', + '>>> 2 + 2', + '4', + '>>> '] self.assert_paint_ignoring_formatting(screen, (12, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) sys.a = 1 self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], - u'1', - u'>>> 1 + 1', - u'2', - u'>>> ', - u' '] + '1', + '>>> 1 + 1', + '2', + '>>> ', + ' '] self.assert_paint_ignoring_formatting(screen) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:-1]) @@ -398,79 +398,79 @@ def test_rewind_history_not_quite_inconsistent(self): self.enter() self.enter("1 + 1") self.enter("2 + 2") - screen = [u">>> for i in range(__import__('sys').a): print(i)", - u'... ', - u'0', - u'1', - u'2', - u'3', - u'4', - u'>>> 1 + 1', - u'2', - u'>>> 2 + 2', - u'4', - u'>>> '] + screen = [">>> for i in range(__import__('sys').a): print(i)", + '... ', + '0', + '1', + '2', + '3', + '4', + '>>> 1 + 1', + '2', + '>>> 2 + 2', + '4', + '>>> '] self.assert_paint_ignoring_formatting(screen, (11, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[7:], (4, 4)) sys.a = 6 self.undo() - screen = [u'5', + screen = ['5', # everything will jump down a line - that's perfectly # reasonable - u'>>> 1 + 1', - u'2', - u'>>> '] + '>>> 1 + 1', + '2', + '>>> '] self.assert_paint_ignoring_formatting(screen, (3, 4)) def test_rewind_barely_consistent(self): self.enter("1 + 1") self.enter("2 + 2") self.enter("3 + 3") - screen = [u">>> 1 + 1", - u'2', - u'>>> 2 + 2', - u'4', - u'>>> 3 + 3', - u'6', - u'>>> '] + screen = [">>> 1 + 1", + '2', + '>>> 2 + 2', + '4', + '>>> 3 + 3', + '6', + '>>> '] self.assert_paint_ignoring_formatting(screen, (6, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[2:], (4, 4)) self.repl.display_lines[2] = self.repl.display_lines[2] * 2 self.undo() - screen = [u'>>> 2 + 2', - u'4', - u'>>> '] + screen = ['>>> 2 + 2', + '4', + '>>> '] self.assert_paint_ignoring_formatting(screen, (2, 4)) def test_clear_screen(self): self.enter("1 + 1") self.enter("2 + 2") - screen = [u">>> 1 + 1", - u'2', - u'>>> 2 + 2', - u'4', - u'>>> '] + screen = [">>> 1 + 1", + '2', + '>>> 2 + 2', + '4', + '>>> '] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.request_paint_to_clear_screen = True - screen = [u">>> 1 + 1", - u'2', - u'>>> 2 + 2', - u'4', - u'>>> ', u'', u'', u'', u''] + screen = [">>> 1 + 1", + '2', + '>>> 2 + 2', + '4', + '>>> ', '', '', '', ''] self.assert_paint_ignoring_formatting(screen, (4, 4)) def test_scroll_down_while_banner_visible(self): self.repl.status_bar.message('STATUS_BAR') self.enter("1 + 1") self.enter("2 + 2") - screen = [u">>> 1 + 1", - u'2', - u'>>> 2 + 2', - u'4', - u'>>> ', - u'STATUS_BAR '] + screen = [">>> 1 + 1", + '2', + '>>> 2 + 2', + '4', + '>>> ', + 'STATUS_BAR '] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:], (3, 4)) @@ -479,23 +479,23 @@ def test_clear_screen_while_banner_visible(self): self.repl.status_bar.message('STATUS_BAR') self.enter("1 + 1") self.enter("2 + 2") - screen = [u">>> 1 + 1", - u'2', - u'>>> 2 + 2', - u'4', - u'>>> ', - u'STATUS_BAR '] + screen = [">>> 1 + 1", + '2', + '>>> 2 + 2', + '4', + '>>> ', + 'STATUS_BAR '] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:], (3, 4)) self.repl.request_paint_to_clear_screen = True - screen = [u'2', - u'>>> 2 + 2', - u'4', - u'>>> ', - u'', u'', u'', - u'STATUS_BAR '] + screen = ['2', + '>>> 2 + 2', + '4', + '>>> ', + '', '', '', + 'STATUS_BAR '] self.assert_paint_ignoring_formatting(screen, (3, 4)) def test_cursor_stays_at_bottom_of_screen(self): @@ -505,22 +505,22 @@ def test_cursor_stays_at_bottom_of_screen(self): self.repl.current_line = "__import__('random').__name__" with output_to_repl(self.repl): self.repl.on_enter(insert_into_history=False) - screen = [u">>> __import__('random').__name__", - u"'random'"] + screen = [">>> __import__('random').__name__", + "'random'"] self.assert_paint_ignoring_formatting(screen) with output_to_repl(self.repl): self.repl.process_event(self.refresh_requests.pop()) - screen = [u">>> __import__('random').__name__", - u"'random'", - u""] + screen = [">>> __import__('random').__name__", + "'random'", + ""] self.assert_paint_ignoring_formatting(screen) with output_to_repl(self.repl): self.repl.process_event(self.refresh_requests.pop()) - screen = [u">>> __import__('random').__name__", - u"'random'", - u">>> "] + screen = [">>> __import__('random').__name__", + "'random'", + ">>> "] self.assert_paint_ignoring_formatting(screen, (2, 4)) def test_unhighlight_paren_bugs(self): @@ -529,8 +529,8 @@ def test_unhighlight_paren_bugs(self): self.assertEqual(self.repl.rl_history.entries, ['']) self.enter('(') self.assertEqual(self.repl.rl_history.entries, ['']) - screen = [u">>> (", - u"... "] + screen = [">>> (", + "... "] self.assertEqual(self.repl.rl_history.entries, ['']) self.assert_paint_ignoring_formatting(screen) self.assertEqual(self.repl.rl_history.entries, ['']) @@ -539,18 +539,18 @@ def test_unhighlight_paren_bugs(self): self.assertEqual(self.repl.rl_history.entries, ['']) self.repl.process_event(')') self.assertEqual(self.repl.rl_history.entries, ['']) - screen = fsarray([cyan(u">>> ")+on_magenta(bold(red('('))), - green(u"... ")+on_magenta(bold(red(')')))]) + screen = fsarray([cyan(">>> ")+on_magenta(bold(red('('))), + green("... ")+on_magenta(bold(red(')')))]) self.assert_paint(screen, (1, 5)) with output_to_repl(self.repl): self.repl.process_event(' ') - screen = fsarray([cyan(u">>> ")+yellow('('), - green(u"... ")+yellow(')')+bold(cyan(" "))]) + screen = fsarray([cyan(">>> ")+yellow('('), + green("... ")+yellow(')')+bold(cyan(" "))]) self.assert_paint(screen, (1, 6)) def send_key(self, key): - self.repl.process_event(u'' if key == ' ' else key) + self.repl.process_event('' if key == ' ' else key) self.repl.paint() # has some side effects we need to be wary of def test_472(self): From 2eca2432a403f284c354b502565b693eb96c7dbb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 17 Feb 2015 22:38:49 +0100 Subject: [PATCH 0472/1650] Also strip at the beginning Signed-off-by: Sebastian Ramacher --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7db296889..b691af065 100755 --- a/setup.py +++ b/setup.py @@ -88,7 +88,7 @@ def git_describe_to_python_version(version): # get version from git describe proc = subprocess.Popen(['git', 'describe', '--tags'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout = proc.communicate()[0].rstrip() + stdout = proc.communicate()[0].strip() if sys.version_info[0] > 2: stdout = stdout.decode('ascii') From 27ad7685fb776e4c408fd5894875a40bfdb156fb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 17 Feb 2015 22:52:09 +0100 Subject: [PATCH 0473/1650] Enable pypy builds in travis Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 4 ++++ .travis.yml | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/.travis.install.sh b/.travis.install.sh index fa3fd1622..77887e702 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -28,6 +28,10 @@ if [[ $RUN == nosetests ]]; then # test specific dependencies pip install mock ;; + pypy) + # test specific dependencies + pip install mock + ;; esac # build and install python setup.py install diff --git a/.travis.yml b/.travis.yml index 73b56480b..beb96d22a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,11 +9,19 @@ python: - "2.7" - "3.3" - "3.4" + - "pypy" + - "pypy3" env: - RUN=nosetests - RUN=build_sphinx +matrix: + fast_finish: true + allow_failures: + - python: "pypy" + - python: "pypy3" + install: - ./.travis.install.sh From f695068c9862528fd73dfc6d98d3343fad866fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Tue, 17 Feb 2015 23:01:50 +0100 Subject: [PATCH 0474/1650] Modernise some uses of assertRaises. --- bpython/test/test_autocomplete.py | 4 ++-- bpython/test/test_keys.py | 6 ++---- bpython/test/test_manual_readline.py | 26 ++++++++++++++++---------- bpython/test/test_repl.py | 10 ++++++---- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 390b73389..90ff8a6de 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -19,8 +19,8 @@ class TestSafeEval(unittest.TestCase): def test_catches_syntax_error(self): - self.assertRaises(autocomplete.EvaluationError, - autocomplete.safe_eval, '1re', {}) + with self.assertRaises(autocomplete.EvaluationError): + autocomplete.safe_eval('1re', {}) class TestFormatters(unittest.TestCase): diff --git a/bpython/test/test_keys.py b/bpython/test/test_keys.py index b7a45d0c2..3fb1f3663 100644 --- a/bpython/test/test_keys.py +++ b/bpython/test/test_keys.py @@ -32,10 +32,9 @@ def test_keymap_getitem(self): def test_keymap_keyerror(self): """Verify keys.KeyMap raising KeyError when getting undefined key""" - def raiser(): + with self.assertRaises(KeyError): keys.cli_key_dispatch['C-asdf'] keys.cli_key_dispatch['C-qwerty'] - self.assertRaises(KeyError, raiser) class TestUrwidKeys(unittest.TestCase): @@ -64,10 +63,9 @@ def test_keymap_getitem(self): def test_keymap_keyerror(self): """Verify keys.KeyMap raising KeyError when getting undefined key""" - def raiser(): + with self.assertRaises(KeyError): keys.urwid_key_dispatch['C-asdf'] keys.urwid_key_dispatch['C-qwerty'] - self.assertRaises(KeyError, raiser) if __name__ == '__main__': diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index c286905b2..d82f96d38 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -239,20 +239,26 @@ def test_seq(self): self.assertEqual(self.edits['a'], f) self.assertEqual(self.edits.call('a', cursor_offset=3, line='hello'), ('hi', 2)) - self.assertRaises(KeyError, self.edits.__getitem__, 'b') - self.assertRaises(KeyError, self.edits.call, 'b') + with self.assertRaises(KeyError): + self.edits['b'] + with self.assertRaises(KeyError): + self.edits.call('b') def test_functions_with_bad_signatures(self): f = lambda something: (1, 2) - self.assertRaises(TypeError, self.edits.add, 'a', f) + with self.assertRaises(TypeError): + self.edits.add('a', f) g = lambda cursor_offset, line, something, something_else: (1, 2) - self.assertRaises(TypeError, self.edits.add, 'a', g) + with self.assertRaises(TypeError): + self.edits.add('a', g) def test_functions_with_bad_return_values(self): f = lambda cursor_offset, line: ('hi',) - self.assertRaises(ValueError, self.edits.add, 'a', f) + with self.assertRaises(ValueError): + self.edits.add('a', f) g = lambda cursor_offset, line: ('hi', 1, 2, 3) - self.assertRaises(ValueError, self.edits.add, 'b', g) + with self.assertRaises(ValueError): + self.edits.add('b', g) def test_config(self): f = lambda cursor_offset, line: ('hi', 2) @@ -267,10 +273,10 @@ class config(object): configured_edits = self.edits.mapping_with_config(config, key_dispatch) self.assertTrue(configured_edits.__contains__, 'c') self.assertNotIn('c', self.edits) - self.assertRaises(NotImplementedError, - configured_edits.add_config_attr, 'att2', g) - self.assertRaises(NotImplementedError, - configured_edits.add, 'd', g) + with self.assertRaises(NotImplementedError): + configured_edits.add_config_attr('att2', g) + with self.assertRaises(NotImplementedError): + configured_edits.add('d', g) self.assertEqual(configured_edits.call('c', cursor_offset=5, line='asfd'), ('hi', 2)) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index cab826aff..a6b2c38a6 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -88,7 +88,8 @@ def test_iter(self): self.assertEqual(list(slice), self.matches * 3) def test_current(self): - self.assertRaises(ValueError, self.matches_iterator.current) + with self.assertRaises(ValueError): + self.matches_iterator.current() next(self.matches_iterator) self.assertEqual(self.matches_iterator.current(), self.matches[0]) @@ -113,7 +114,8 @@ def test_cur_line(self): self.matches_iterator.orig_line) self.matches_iterator.completer = completer - self.assertRaises(ValueError, self.matches_iterator.cur_line) + with self.assertRaises(ValueError): + self.matches_iterator.cur_line() self.assertEqual(next(self.matches_iterator), self.matches[0]) self.assertEqual(self.matches_iterator.cur_line(), @@ -215,8 +217,8 @@ def set_input_line(self, line): def assert_get_source_error_for_current_function(self, func, msg): self.repl.current_func = func - self.assertRaises(repl.SourceNotFound, - self.repl.get_source_of_current_name) + with self.assertRaises(repl.SourceNotFound): + self.repl.get_source_of_current_name() try: self.repl.get_source_of_current_name() except repl.SourceNotFound as e: From e7e17366a0fe369a4153e6685875602ead118f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20St=C3=BChrk?= Date: Tue, 17 Feb 2015 23:06:58 +0100 Subject: [PATCH 0475/1650] Tighten a test by adding a fail(). --- bpython/test/test_repl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index a6b2c38a6..c276710b0 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -223,6 +223,8 @@ def assert_get_source_error_for_current_function(self, func, msg): self.repl.get_source_of_current_name() except repl.SourceNotFound as e: self.assertEqual(e.args[0], msg) + else: + self.fail("Should have raised SourceNotFound") def test_current_function(self): self.set_input_line('INPUTLINE') From fcd18378d7c67ef7f791dd4ecdb301174cc8dc2a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 17 Feb 2015 23:43:33 +0100 Subject: [PATCH 0476/1650] Remove workarounds for no longer supported Python versions collections.Callable was used for Python 3.0 - 3.1 since callable was missing in these versions. Signed-off-by: Sebastian Ramacher --- bpython/inspection.py | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 0bc48be67..3d50c5c0c 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -25,12 +25,10 @@ # from __future__ import with_statement -import collections import inspect import io import keyword import pydoc -import types from six.moves import range from pygments.token import Token @@ -38,18 +36,9 @@ from bpython._py3compat import PythonLexer, py3 from bpython.lazyre import LazyReCompile -try: - collections.Callable - has_collections_callable = True -except AttributeError: - has_collections_callable = False -try: - types.InstanceType - has_instance_type = True -except AttributeError: - has_instance_type = False - if not py3: + import types + _name = LazyReCompile(r'[a-zA-Z_]\w*$') @@ -272,13 +261,7 @@ def is_eval_safe_name(string): def is_callable(obj): - if has_instance_type and isinstance(obj, types.InstanceType): - # Work around a CPython bug, see CPython issue #7624 - return callable(obj) - elif has_collections_callable: - return isinstance(obj, collections.Callable) - else: - return callable(obj) + return callable(obj) get_encoding_re = LazyReCompile(r'coding[:=]\s*([-\w.]+)') From 6870362713f69f5e0b901b4f42f3b609e2c352fa Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 03:44:07 +0100 Subject: [PATCH 0477/1650] PEP-8 Signed-off-by: Sebastian Ramacher --- bpdb/__init__.py | 5 ++++- bpdb/debugger.py | 8 +++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index 1db4d493a..de8be103b 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -41,6 +41,7 @@ def set_trace(): debugger = BPdb() debugger.set_trace(sys._getframe().f_back) + # Adopted verbatim from pdb for completeness: def post_mortem(t=None): @@ -57,9 +58,11 @@ def post_mortem(t=None): p.reset() p.interaction(None, t) + def pm(): post_mortem(getattr(sys, "last_traceback", None)) + def main(): parser = OptionParser( usage='Usage: %prog [options] [file [args]]') @@ -73,7 +76,7 @@ def main(): 'See AUTHORS for detail.') return 0 - # The following code is baed on Python's pdb.py. + # The following code is based on Python's pdb.py. mainpyfile = args[1] if not os.path.exists(mainpyfile): print('Error:', mainpyfile, 'does not exist') diff --git a/bpdb/debugger.py b/bpdb/debugger.py index 6f66dd15b..60b51b853 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -25,6 +25,7 @@ import pdb import bpython + class BPdb(pdb.Pdb): """ PDB with BPython support. """ @@ -39,20 +40,17 @@ def postloop(self): self.intro = None pdb.Pdb.postloop(self) - ### cmd.Cmd commands - + # cmd.Cmd commands def do_Bpython(self, arg): bpython.embed(self.curframe.f_locals, ['-i']) - def help_Bpython(self): print("B(python)") print("") print("Invoke the bpython interpreter for this stack frame. To exit " "bpython and return to a standard pdb press Ctrl-d") - - ### shortcuts + # shortcuts do_B = do_Bpython help_B = help_Bpython From a5eefc2a4013b1317150cd8e8303b72559b2243c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 03:44:29 +0100 Subject: [PATCH 0478/1650] Use objects and pass SystemExit error codes Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/coderunner.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 7255e3d1e..1d158be02 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -50,6 +50,9 @@ class Unfinished(RequestFromCodeGreenlet): class SystemExitRequest(RequestFromCodeGreenlet): """Running code raised a SystemExit""" + def __init__(self, args): + self.args = args + class CodeRunner(object): """Runs user code in an interpreter. @@ -139,22 +142,22 @@ def run_code(self, for_code=None): request = self.code_greenlet.switch(for_code) logger.debug('request received from code was %r', request) - if not issubclass(request, RequestFromCodeGreenlet): + if not isinstance(request, RequestFromCodeGreenlet): raise ValueError("Not a valid value from code greenlet: %r" % request) - if request in [Wait, Refresh]: + if isinstance(request, (Wait, Refresh)): self.code_is_waiting = True - if request == Refresh: + if isinstance(request, Refresh): self.request_refresh() return False - elif request in [Done, Unfinished]: + elif isinstance(request, (Done, Unfinished)): self._unload_code() signal.signal(signal.SIGINT, self.orig_sigint_handler) self.orig_sigint_handler = None return request - elif request in [SystemExitRequest]: + elif isinstance(request, SystemExitRequest): self._unload_code() - raise SystemExitFromCodeGreenlet() + raise SystemExitFromCodeGreenlet(request.args) def sigint_handler(self, *args): """SIGINT handler to use while code is running or request being @@ -170,9 +173,9 @@ def sigint_handler(self, *args): def _blocking_run_code(self): try: unfinished = self.interp.runsource(self.source) - except SystemExit: - return SystemExitRequest - return Unfinished if unfinished else Done + except SystemExit as e: + return SystemExitRequest(e.args) + return Unfinished() if unfinished else Done() def request_from_main_greenlet(self, force_refresh=False): """Return the argument passed in to .run_code(for_code) @@ -180,9 +183,9 @@ def request_from_main_greenlet(self, force_refresh=False): Nothing means calls to run_code must be... ??? """ if force_refresh: - value = self.main_greenlet.switch(Refresh) + value = self.main_greenlet.switch(Refresh()) else: - value = self.main_greenlet.switch(Wait) + value = self.main_greenlet.switch(Wait()) if value is SigintHappened: raise KeyboardInterrupt() return value From 8f938eb47b96bd224d642d16b15dfec98b7b5ef3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 03:44:46 +0100 Subject: [PATCH 0479/1650] Return exit codes Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index b80fd5b65..550601730 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -19,6 +19,7 @@ from bpython.importcompletion import find_iterator from bpython.curtsiesfrontend import events as bpythonevents from bpython import inspection +from bpython.repl import extract_exit_value logger = logging.getLogger(__name__) @@ -55,7 +56,7 @@ def main(args=None, locals_=None, banner=None): if exec_args: if not options: raise ValueError("don't pass in exec_args without options") - exit_value = 0 + exit_value = () if options.paste: paste = curtsies.events.PasteEvent() encoding = inspection.get_encoding_file(exec_args[0]) @@ -69,14 +70,18 @@ def main(args=None, locals_=None, banner=None): except SystemExit as e: exit_value = e.args if not options.interactive: - raise SystemExit(exit_value) + return extract_exit_value(exit_value) else: # expected for interactive sessions (vanilla python does it) sys.path.insert(0, '') print(bpargs.version_banner()) - mainloop(config, locals_, banner, interp, paste, - interactive=(not exec_args)) + try: + exit_value = mainloop(config, locals_, banner, interp, paste, + interactive=(not exec_args)) + except (SystemExitFromCodeGreenlet, SystemExit) as e: + exit_value = e.args + return extract_exit_value(exit_value) def mainloop(config, locals_, banner, interp=None, paste=None, From 1834f70df0877240f3661ded3b811aa7a0f12d7c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 03:45:17 +0100 Subject: [PATCH 0480/1650] Switch bpython.embed to curtsies (fixes #484) Signed-off-by: Sebastian Ramacher --- bpython/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/__init__.py b/bpython/__init__.py index 505ef61e2..8d68d209c 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -32,5 +32,5 @@ def embed(locals_=None, args=['-i', '-q'], banner=None): - from bpython.cli import main + from bpython.curtsies import main return main(args, locals_, banner) From 33dae4f74a76710e7aab8af2562e212a0f3d3a2e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 03:46:00 +0100 Subject: [PATCH 0481/1650] Update changelog --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 5268145ff..1325baa9f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,8 @@ New features: Fixes: +* #484: Switch `bpython.embed` to the curtsies frontend. + 0.14 ---- From 43e70389badc48be2986a606f8f7b3f2aa29d59b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 18:07:46 +0100 Subject: [PATCH 0482/1650] Use correct default level values (fixes #486) Python 2 uses -1, Python 3 uses 0. Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 95704fa34..872ecaa35 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -420,9 +420,11 @@ def __enter__(self): if self.watcher: # for reading modules if they fail to load old_module_locations = {} + default_level = 0 if py3 else -1 @functools.wraps(self.orig_import) - def new_import(name, globals={}, locals={}, fromlist=[], level=-1): + def new_import(name, globals={}, locals={}, fromlist=[], + level=default_level): try: m = self.orig_import(name, globals=globals, locals=locals, fromlist=fromlist, level=level) From 1b4cc8fab26707db842c0bcdcf28d75d19601123 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 18:08:04 +0100 Subject: [PATCH 0483/1650] Use six Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/sitefix.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/sitefix.py b/bpython/curtsiesfrontend/sitefix.py index 37a6ae954..a8cf8a38b 100644 --- a/bpython/curtsiesfrontend/sitefix.py +++ b/bpython/curtsiesfrontend/sitefix.py @@ -1,6 +1,6 @@ import sys -from bpython._py3compat import py3 +from six.moves import builtins def resetquit(builtins): @@ -15,4 +15,4 @@ def __call__(self, code=None): def monkeypatch_quit(): if 'site' in sys.modules: - resetquit(sys.modules['builtins' if py3 else '__builtin__']) + resetquit(builtins) From 2df245bab844f2b61cd6e0d6364d23fd316df375 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 18:45:52 +0100 Subject: [PATCH 0484/1650] Use builtins module Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 872ecaa35..7ee9efb9d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -15,7 +15,7 @@ import threading import time import unicodedata -from six.moves import range +from six.moves import range, builtins from pygments import format from bpython._py3compat import PythonLexer @@ -416,7 +416,7 @@ def __enter__(self): signal.signal(signal.SIGWINCH, self.sigwinch_handler) signal.signal(signal.SIGTSTP, self.sigtstp_handler) - self.orig_import = __builtins__['__import__'] + self.orig_import = builtins.__import__ if self.watcher: # for reading modules if they fail to load old_module_locations = {} @@ -438,7 +438,7 @@ def new_import(name, globals={}, locals={}, fromlist=[], old_module_locations[name] = m.__file__ self.watcher.track_module(m.__file__) return m - __builtins__['__import__'] = new_import + builtins.__import__ = new_import sitefix.monkeypatch_quit() return self @@ -449,7 +449,7 @@ def __exit__(self, *args): sys.stderr = self.orig_stderr signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) signal.signal(signal.SIGTSTP, self.orig_sigtstp_handler) - __builtins__['__import__'] = self.orig_import + builtins.__import__ = self.orig_import def sigwinch_handler(self, signum, frame): old_rows, old_columns = self.height, self.width From eb56295ea21cda27ebd69a772fd526e6071f960e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 17 Feb 2015 15:26:06 -0500 Subject: [PATCH 0485/1650] GlobalCompleter returns unicode matches --- bpython/_py3compat.py | 10 ++++++++++ bpython/autocomplete.py | 10 ++++++++-- bpython/importcompletion.py | 17 +++-------------- bpython/test/test_autocomplete.py | 11 +++++++++++ 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index b6d4b5389..adbc5889f 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -49,3 +49,13 @@ def prepare_for_exec(arg, encoding=None): else: def prepare_for_exec(arg, encoding=None): return arg.encode(encoding) + + +def try_decode(s, encoding): + """Try to decode s which is str names. Return None if not decodable""" + if not py3 and not isinstance(s, unicode): + try: + return s.decode(encoding) + except UnicodeDecodeError: + return None + return s diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index fe7bb27f3..45d0a5020 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -34,7 +34,7 @@ from bpython import inspection from bpython import importcompletion from bpython import line as lineparts -from bpython._py3compat import py3 +from bpython._py3compat import py3, try_decode from bpython.lazyre import LazyReCompile @@ -286,11 +286,17 @@ def matches(self, cursor_offset, line, **kwargs): n = len(text) for word in keyword.kwlist: if method_match(word, n, text): - matches.add(word) + word = try_decode(word, 'ascii') # py2 keywords are all ascii + if word is not None: + matches.add(word) for nspace in [builtins.__dict__, locals_]: for word, val in nspace.items(): if (method_match(word, len(text), text) and word != "__builtins__"): + word = try_decode(word, 'ascii') + # if identifier isn't ascii, don't complete (syntax error) + if word is None: + continue matches.add(_callable_postfix(val, word)) return matches diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 4b0c55153..fd95872b2 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from bpython._py3compat import py3 +from bpython._py3compat import py3, try_decode from bpython.line import current_word, current_import, \ current_from_import_from, current_from_import_import @@ -42,17 +42,6 @@ fully_loaded = False -def try_decode_module(module, encoding): - """Try to decode module names.""" - if not py3 and not isinstance(module, unicode): - try: - return module.decode(encoding) - except UnicodeDecodeError: - # Not importable anyway, ignore it - return None - return module - - def module_matches(cw, prefix=''): """Modules names to replace cw with""" full = '%s.%s' % (prefix, cw) if prefix else cw @@ -83,7 +72,7 @@ def attr_matches(cw, prefix='', only_modules=False): if module_part: matches = ('%s.%s' % (module_part, m) for m in matches) - generator = (try_decode_module(match, 'ascii') for match in matches) + generator = (try_decode(match, 'ascii') for match in matches) return set(filter(lambda x: x is not None, generator)) @@ -184,7 +173,7 @@ def find_all_modules(path=None): if not p: p = os.curdir for module in find_modules(p): - module = try_decode_module(module, sys.getfilesystemencoding()) + module = try_decode(module, sys.getfilesystemencoding()) if module is None: continue modules.add(module) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 90ff8a6de..d4a6a49b1 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + from collections import namedtuple import inspect from bpython._py3compat import py3 @@ -273,6 +275,15 @@ def function(): locals_={'function': function}), set(('function(', ))) + def test_completions_are_unicode(self): + for m in self.com.matches(1, 'a', locals_={'abc': 10}): + self.assertIsInstance(m, type(u'')) + + @unittest.skipIf(py3, "in Python 3 invalid identifiers are passed through") + def test_ignores_nonascii_encodable(self): + self.assertSetEqual(self.com.matches(1, 'abc', locals_={'abcß': 10}), + set()) + class TestParameterNameCompletion(unittest.TestCase): def test_set_of_params_returns_when_matches_found(self): From 3f6eb0949b9f608ad2acbb4d7e71553c1a38adee Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 17 Feb 2015 16:30:31 -0500 Subject: [PATCH 0486/1650] import completion returns unicode --- bpython/importcompletion.py | 5 +++-- bpython/test/test_importcompletion.py | 11 ++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index fd95872b2..b23994908 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -166,14 +166,15 @@ def find_all_modules(path=None): """Return a list with all modules in `path`, which should be a list of directory names. If path is not given, sys.path will be used.""" if path is None: - modules.update(sys.builtin_module_names) + modules.update(try_decode(m, 'ascii') + for m in sys.builtin_module_names) path = sys.path for p in path: if not p: p = os.curdir for module in find_modules(p): - module = try_decode(module, sys.getfilesystemencoding()) + module = try_decode(module, 'ascii') if module is None: continue modules.add(module) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 012ae52a5..d8b6c288f 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -10,26 +10,27 @@ class TestSimpleComplete(unittest.TestCase): def setUp(self): self.original_modules = importcompletion.modules - importcompletion.modules = ['zzabc', 'zzabd', 'zzefg', 'zzabc.e', - 'zzabc.f'] + importcompletion.modules = [u'zzabc', u'zzabd', u'zzefg', u'zzabc.e', + u'zzabc.f'] def tearDown(self): importcompletion.modules = self.original_modules def test_simple_completion(self): self.assertSetEqual(importcompletion.complete(10, 'import zza'), - set(['zzabc', 'zzabd'])) + set([u'zzabc', u'zzabd'])) def test_package_completion(self): self.assertSetEqual(importcompletion.complete(13, 'import zzabc.'), - set(['zzabc.e', 'zzabc.f'])) + set([u'zzabc.e', u'zzabc.f'])) class TestRealComplete(unittest.TestCase): @classmethod def setUpClass(cls): - [_ for _ in importcompletion.find_iterator] + for _ in importcompletion.find_iterator: + pass __import__('sys') __import__('os') From 3cfac865c915269ba593c1e9ea1442c6b6f6f7d0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 17 Feb 2015 19:08:55 -0500 Subject: [PATCH 0487/1650] switch to curtsies v2.2, fmtstrs unicode --- bpython/curtsiesfrontend/replpainter.py | 4 +++- setup.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 02911e0d0..649a6984f 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -108,12 +108,14 @@ def formatted_argspec(argspec, columns, config): color = bolds[color] if not py3: - s += color(inspect.strseq(arg, str)) + s += color(inspect.strseq(arg, unicode)) else: s += color(arg) if kw is not None: s += punctuation_color('=') + if not py3: + kw = kw.decode('ascii', 'replace') s += token_color(kw) if i != len(args) - 1: diff --git a/setup.py b/setup.py index b691af065..845799d1d 100755 --- a/setup.py +++ b/setup.py @@ -210,7 +210,7 @@ def initialize_options(self): install_requires = [ 'pygments', 'requests', - 'curtsies >=0.1.18, <0.2.0', + 'curtsies >=0.1.18', 'greenlet', 'six >=1.4' ] From 79f75dc36ff7056f83baa5077c7e17c494553244 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 18 Feb 2015 10:22:06 -0500 Subject: [PATCH 0488/1650] use unicode literals --- bpython/test/test_importcompletion.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index d8b6c288f..cd301f504 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + from bpython import importcompletion try: @@ -10,19 +12,19 @@ class TestSimpleComplete(unittest.TestCase): def setUp(self): self.original_modules = importcompletion.modules - importcompletion.modules = [u'zzabc', u'zzabd', u'zzefg', u'zzabc.e', - u'zzabc.f'] + importcompletion.modules = ['zzabc', 'zzabd', 'zzefg', 'zzabc.e', + 'zzabc.f'] def tearDown(self): importcompletion.modules = self.original_modules def test_simple_completion(self): self.assertSetEqual(importcompletion.complete(10, 'import zza'), - set([u'zzabc', u'zzabd'])) + set(['zzabc', 'zzabd'])) def test_package_completion(self): self.assertSetEqual(importcompletion.complete(13, 'import zzabc.'), - set([u'zzabc.e', u'zzabc.f'])) + set(['zzabc.e', 'zzabc.f'])) class TestRealComplete(unittest.TestCase): From 8d6ba1c836c5c6a70bbec7b86fec8cb99e6b59ea Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 18 Feb 2015 12:17:58 -0500 Subject: [PATCH 0489/1650] take simpler approach to decoding keywords --- bpython/autocomplete.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 45d0a5020..b0ee29f20 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -286,9 +286,9 @@ def matches(self, cursor_offset, line, **kwargs): n = len(text) for word in keyword.kwlist: if method_match(word, n, text): - word = try_decode(word, 'ascii') # py2 keywords are all ascii - if word is not None: - matches.add(word) + if not py3: + word = word.decode('ascii') # py2 keywords are all ascii + matches.add(word) for nspace in [builtins.__dict__, locals_]: for word, val in nspace.items(): if (method_match(word, len(text), text) and From ac3ef2065485d930109b2ba484513d6e80e0373d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 19:21:15 +0100 Subject: [PATCH 0490/1650] Use correct length Signed-off-by: Sebastian Ramacher --- bpython/test/test_autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index d4a6a49b1..29b777ef0 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -281,7 +281,7 @@ def test_completions_are_unicode(self): @unittest.skipIf(py3, "in Python 3 invalid identifiers are passed through") def test_ignores_nonascii_encodable(self): - self.assertSetEqual(self.com.matches(1, 'abc', locals_={'abcß': 10}), + self.assertSetEqual(self.com.matches(3, 'abc', locals_={'abcß': 10}), set()) From b454048faba275f32e78eaf2970e8b9b34decdff Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 19:21:38 +0100 Subject: [PATCH 0491/1650] Test global completion with modified keyword list Signed-off-by: Sebastian Ramacher --- bpython/test/test_autocomplete.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 29b777ef0..b262990b2 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -2,6 +2,7 @@ from collections import namedtuple import inspect +import keyword from bpython._py3compat import py3 try: @@ -284,6 +285,14 @@ def test_ignores_nonascii_encodable(self): self.assertSetEqual(self.com.matches(3, 'abc', locals_={'abcß': 10}), set()) + def test_mock_kwlist(self): + with mock.patch.object(keyword, 'kwlist', new=['abcd']): + self.assertSetEqual(self.com.matches(3, 'abc', locals_={}), set()) + + def test_mock_kwlist_non_ascii(self): + with mock.patch.object(keyword, 'kwlist', new=['abcß']): + self.assertSetEqual(self.com.matches(3, 'abc', locals_={}), set()) + class TestParameterNameCompletion(unittest.TestCase): def test_set_of_params_returns_when_matches_found(self): From d2d236d71afcd7c3a59002c8f359c4394e984aa6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 19:21:58 +0100 Subject: [PATCH 0492/1650] Keep a frozen copy of keyword list Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index b0ee29f20..628d5dac8 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -53,6 +53,11 @@ "div", "truediv", "neg", "pos", "abs", "invert", "complex", "int", "float", "oct", "hex", "index", "coerce", "enter", "exit")) +if py3: + KEYWORDS = frozenset(keyword.kwlist) +else: + KEYWORDS = frozenset(name.decode('ascii') for name in keyword.kwlist) + def after_last_dot(name): return name.rstrip('.').rsplit('.')[-1] @@ -284,10 +289,8 @@ def matches(self, cursor_offset, line, **kwargs): matches = set() n = len(text) - for word in keyword.kwlist: + for word in KEYWORDS: if method_match(word, n, text): - if not py3: - word = word.decode('ascii') # py2 keywords are all ascii matches.add(word) for nspace in [builtins.__dict__, locals_]: for word, val in nspace.items(): From 291a5d363dce53964ed122fd6817a17557cb9478 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 18 Feb 2015 16:24:56 -0500 Subject: [PATCH 0493/1650] fix #483 temporarily --- bpython/autocomplete.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 628d5dac8..2025c58b8 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -367,6 +367,7 @@ def matches(self, cursor_offset, line, **kwargs): if not lineparts.current_word(cursor_offset, line): return None history = '\n'.join(history) + '\n' + line + try: script = jedi.Script(history, len(history.splitlines()), cursor_offset, 'fake.py') @@ -374,6 +375,11 @@ def matches(self, cursor_offset, line, **kwargs): except jedi.NotFoundError: self._orig_start = None return None + except IndexError: + # for https://github.com/bpython/bpython/issues/483 + self._orig_start = None + return None + if completions: diff = len(completions[0].name) - len(completions[0].complete) self._orig_start = cursor_offset - diff From cc7aebd3373154eb2ce29a87b564821b34debc30 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 23:13:51 +0100 Subject: [PATCH 0494/1650] Use sys.meta_path to hook into import So we do not need to care about the default values of arguments to __import__ anymore. Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 54 ++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 7ee9efb9d..2a6d70d60 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -213,6 +213,34 @@ def readline(self): return value +class ImportLoader(object): + + def __init__(self, watcher, loader): + self.watcher = watcher + self.loader = loader + + def load_module(self, fullname): + module = self.loader.load_module(fullname) + if hasattr(module, '__file__'): + self.watcher.track_module(module.__file__) + return module + + +class ImportFinder(object): + + def __init__(self, watcher, old_meta_path): + self.watcher = watcher + self.old_meta_path = old_meta_path + + def find_module(self, fullname, path=None): + for finder in self.old_meta_path: + loader = finder.find_module(fullname, path) + if loader is not None: + return ImportLoader(self.watcher, loader) + + return None + + class Repl(BpythonRepl): """Python Repl @@ -416,29 +444,9 @@ def __enter__(self): signal.signal(signal.SIGWINCH, self.sigwinch_handler) signal.signal(signal.SIGTSTP, self.sigtstp_handler) - self.orig_import = builtins.__import__ + self.orig_meta_path = sys.meta_path if self.watcher: - # for reading modules if they fail to load - old_module_locations = {} - default_level = 0 if py3 else -1 - - @functools.wraps(self.orig_import) - def new_import(name, globals={}, locals={}, fromlist=[], - level=default_level): - try: - m = self.orig_import(name, globals=globals, locals=locals, - fromlist=fromlist, level=level) - except: - if name in old_module_locations: - loc = old_module_locations[name] - self.watcher.track_module(loc) - raise - else: - if hasattr(m, "__file__"): - old_module_locations[name] = m.__file__ - self.watcher.track_module(m.__file__) - return m - builtins.__import__ = new_import + sys.meta_path = [ImportFinder(self.watcher, self.orig_meta_path)] sitefix.monkeypatch_quit() return self @@ -449,7 +457,7 @@ def __exit__(self, *args): sys.stderr = self.orig_stderr signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) signal.signal(signal.SIGTSTP, self.orig_sigtstp_handler) - builtins.__import__ = self.orig_import + sys.meta_path = self.orig_meta_path def sigwinch_handler(self, signum, frame): old_rows, old_columns = self.height, self.width From 84b34ef1d6ca69d1187ad9e2981e841eaa1d1483 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 18 Feb 2015 23:25:29 +0100 Subject: [PATCH 0495/1650] Remove fast_finish Let's hope that this means less spam. Signed-off-by: Sebastian Ramacher --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index beb96d22a..fa6d1945b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,6 @@ env: - RUN=build_sphinx matrix: - fast_finish: true allow_failures: - python: "pypy" - python: "pypy3" From 89428073340151356027e97212cbe7449f4869ad Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 18 Feb 2015 18:58:51 -0500 Subject: [PATCH 0496/1650] Decode output of stdout.write in Python 2 --- bpython/curtsiesfrontend/coderunner.py | 13 +++++++++++-- bpython/test/test_curtsies_coderunner.py | 12 ++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 1d158be02..13921ad3c 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -15,6 +15,9 @@ import greenlet import logging +from bpython._py3compat import py3, try_decode +from bpython.config import getpreferredencoding + logger = logging.getLogger(__name__) @@ -193,11 +196,17 @@ def request_from_main_greenlet(self, force_refresh=False): class FakeOutput(object): def __init__(self, coderunner, on_write): + """Fakes sys.stdout or sys.stderr + + on_write should always take unicode + """ self.coderunner = coderunner self.on_write = on_write - def write(self, *args, **kwargs): - self.on_write(*args, **kwargs) + def write(self, s, *args, **kwargs): + if not py3 and isinstance(s, str): + s = s.decode(getpreferredencoding()) + self.on_write(s, *args, **kwargs) return self.coderunner.request_from_main_greenlet(force_refresh=True) def writelines(self, l): diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index 472092380..2446ecc65 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -1,5 +1,7 @@ import sys +from bpython.test import mock + try: import unittest2 as unittest except ImportError: @@ -43,3 +45,13 @@ def ctrlc(): sys.stderr = stderr c.load_code('1 + 1') c.run_code() + + +class TestFakeOutput(unittest.TestCase): + + def assert_unicode(self, s): + self.assertIsInstance(s, type(u'')) + + def test_bytes(self): + out = FakeOutput(mock.Mock(), self.assert_unicode) + out.write('native string type') From 6e38d183a6433667be95754c0c572c071e1982aa Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 19 Feb 2015 03:25:33 +0100 Subject: [PATCH 0497/1650] Make try_decode a no-op in Python 3 Signed-off-by: Sebastian Ramacher --- bpython/_py3compat.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index adbc5889f..8082b8367 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -51,11 +51,15 @@ def prepare_for_exec(arg, encoding=None): return arg.encode(encoding) -def try_decode(s, encoding): - """Try to decode s which is str names. Return None if not decodable""" - if not py3 and not isinstance(s, unicode): - try: - return s.decode(encoding) - except UnicodeDecodeError: - return None - return s +if py3: + def try_decode(s, encoding): + return s +else: + def try_decode(s, encoding): + """Try to decode s which is str names. Return None if not decodable""" + if not isinstance(s, unicode): + try: + return s.decode(encoding) + except UnicodeDecodeError: + return None + return s From f22e580a61da25af9df11df3bc159f481beb4546 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 19 Feb 2015 03:56:22 +0100 Subject: [PATCH 0498/1650] Remove useless u Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2a6d70d60..02503b910 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -56,9 +56,9 @@ logger = logging.getLogger(__name__) -INCONSISTENT_HISTORY_MSG = u"#<---History inconsistent with output shown--->" -CONTIGUITY_BROKEN_MSG = u"#<---History contiguity broken by rewind--->" -HELP_MESSAGE = u""" +INCONSISTENT_HISTORY_MSG = "#<---History inconsistent with output shown--->" +CONTIGUITY_BROKEN_MSG = "#<---History contiguity broken by rewind--->" +HELP_MESSAGE = """ Thanks for using bpython! See http://bpython-interpreter.org/ for more information and @@ -771,7 +771,7 @@ def down_one_line(self): def process_simple_keypress(self, e): # '\n' needed for pastes - if e in (u"", u"", u"", u"\n", u"\r"): + if e in ("", "", "", "\n", "\r"): self.on_enter() while self.fake_refresh_requested: self.fake_refresh_requested = False @@ -796,14 +796,14 @@ def send_current_block_to_external_editor(self, filename=None): self.cursor_offset = len(self.current_line) def send_session_to_external_editor(self, filename=None): - for_editor = (u"### current bpython session - file will be " - u"reevaluated, ### lines will not be run\n") - for_editor += u'\n'.join(line[len(self.ps1):] - if line.startswith(self.ps1) else - line[len(self.ps2):] - if line.startswith(self.ps2) else - '### '+line - for line in self.getstdout().split('\n')) + for_editor = ("### current bpython session - file will be " + "reevaluated, ### lines will not be run\n") + for_editor += '\n'.join(line[len(self.ps1):] + if line.startswith(self.ps1) else + line[len(self.ps2):] + if line.startswith(self.ps2) else + '### '+line + for line in self.getstdout().split('\n')) text = self.send_to_external_editor(for_editor) lines = text.split('\n') from_editor = [line for line in lines if line[:4] != '### '] @@ -1557,8 +1557,8 @@ def just_simple_events(event_list): simple_events = [] for e in event_list: # '\n' necessary for pastes - if e in (u"", u"", u"", u"\n", u"\r"): - simple_events.append(u'\n') + if e in ("", "", "", "\n", "\r"): + simple_events.append('\n') elif isinstance(e, events.Event): pass # ignore events elif e == '': From c08f41297ea8dd386815a732b7fa320de26689cc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 19 Feb 2015 04:06:21 +0100 Subject: [PATCH 0499/1650] Remove no longer needed __future__ imports Signed-off-by: Sebastian Ramacher --- bpython/args.py | 2 +- bpython/cli.py | 2 +- bpython/config.py | 1 - bpython/inspection.py | 1 - bpython/repl.py | 1 - bpython/urwid.py | 2 +- 6 files changed, 3 insertions(+), 6 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 6f2e9ef29..6fd2b519c 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -2,7 +2,7 @@ Module to handle command line argument parsing, for all front-ends. """ -from __future__ import with_statement, print_function +from __future__ import print_function import os import sys import code diff --git a/bpython/cli.py b/bpython/cli.py index 72c5a1dd4..24a2e97bb 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -39,7 +39,7 @@ # - Instead the suspend key exits the program # - View source doesn't work on windows unless you install the less program (From GnuUtils or Cygwin) -from __future__ import division, with_statement +from __future__ import division import platform import os diff --git a/bpython/config.py b/bpython/config.py index 74b0daa99..13c7a880c 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -1,6 +1,5 @@ # encoding: utf-8 -from __future__ import with_statement import os import sys import locale diff --git a/bpython/inspection.py b/bpython/inspection.py index 3d50c5c0c..b188dc6c7 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -24,7 +24,6 @@ # THE SOFTWARE. # -from __future__ import with_statement import inspect import io import keyword diff --git a/bpython/repl.py b/bpython/repl.py index 2fd783830..fe1b9e543 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import with_statement import code import errno import inspect diff --git a/bpython/urwid.py b/bpython/urwid.py index 60390ace5..f10ceb60b 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -33,7 +33,7 @@ """ -from __future__ import absolute_import, with_statement, division, print_function +from __future__ import absolute_import, division, print_function import sys import os From 49818b9a12ccaadeca14f83897cd2addb490074a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 19 Feb 2015 05:29:04 +0100 Subject: [PATCH 0500/1650] Sort imports Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 2 +- bpython/repl.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 550601730..88dc55009 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -1,9 +1,9 @@ from __future__ import absolute_import import code +import io import logging import sys -import io from optparse import Option import curtsies diff --git a/bpython/repl.py b/bpython/repl.py index fe1b9e543..d3c2fd9f6 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -47,13 +47,13 @@ from pygments.token import Token +from bpython import autocomplete from bpython import inspection from bpython._py3compat import PythonLexer, py3, prepare_for_exec -from bpython.formatter import Parenthesis -from bpython.translations import _, ngettext from bpython.clipboard import get_clipboard, CopyFailed +from bpython.formatter import Parenthesis from bpython.history import History -import bpython.autocomplete as autocomplete +from bpython.translations import _, ngettext class RuntimeTimer(object): From 8aa4ddfefbb6963d325e7aa6ef06c0ab8a9f5d01 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 19 Feb 2015 05:29:55 +0100 Subject: [PATCH 0501/1650] Build wheel Signed-off-by: Sebastian Ramacher --- setup.cfg | 2 ++ setup.py | 15 +++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..2a9acf13d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/setup.py b/setup.py index 845799d1d..22ca6e9f5 100755 --- a/setup.py +++ b/setup.py @@ -219,6 +219,13 @@ def initialize_options(self): 'urwid': ['urwid'], 'watch': ['watchdog'], 'jedi': ['jedi'], + # need PyOpenSSL for SNI support (only 2.X and on Darwin) + # list of packages taken from + # https://github.com/kennethreitz/requests/blob/master/requests/packages/urllib3/contrib/pyopenssl.py + ':sys_platform == "darwin" and '\ + '(python_version == "2.6" or python_version == "2.7")': [ + 'PyOpenSSL', 'ndg-httpsclient', 'pyasn1' + ] } packages = [ @@ -239,14 +246,6 @@ def initialize_options(self): ] } -if sys.version_info[0] == 2 and sys.platform == "darwin": - # need PyOpenSSL for SNI support (only 2.X and on Darwin) - # list of packages taken from - # https://github.com/kennethreitz/requests/blob/master/requests/packages/urllib3/contrib/pyopenssl.py - install_requires.append('PyOpenSSL') - install_requires.append('ndg-httpsclient') - install_requires.append('pyasn1') - tests_require = [] if sys.version_info[0] == 2 and sys.version_info[1] < 7: tests_require.append('unittest2') From be4cd1c7dc68fdc9b0ccf691fab39a4cab8c7048 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 19 Feb 2015 22:37:36 +0100 Subject: [PATCH 0502/1650] Remove unused import Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/coderunner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 13921ad3c..ad55683c9 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -15,7 +15,7 @@ import greenlet import logging -from bpython._py3compat import py3, try_decode +from bpython._py3compat import py3 from bpython.config import getpreferredencoding logger = logging.getLogger(__name__) From 88fa2097826126368411426817c4c7f441b45ca4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 19 Feb 2015 22:37:47 +0100 Subject: [PATCH 0503/1650] Decode with errors='replace' Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/coderunner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index ad55683c9..f06f7a190 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -205,7 +205,7 @@ def __init__(self, coderunner, on_write): def write(self, s, *args, **kwargs): if not py3 and isinstance(s, str): - s = s.decode(getpreferredencoding()) + s = s.decode(getpreferredencoding(), errors='replace') self.on_write(s, *args, **kwargs) return self.coderunner.request_from_main_greenlet(force_refresh=True) From 046646e7639fbdd8965ab0b2925189453dfdc51d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 19 Feb 2015 22:56:35 +0100 Subject: [PATCH 0504/1650] Use ignore rather than replace Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/coderunner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index f06f7a190..21fdbc18a 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -205,7 +205,7 @@ def __init__(self, coderunner, on_write): def write(self, s, *args, **kwargs): if not py3 and isinstance(s, str): - s = s.decode(getpreferredencoding(), errors='replace') + s = s.decode(getpreferredencoding(), 'ignore') self.on_write(s, *args, **kwargs) return self.coderunner.request_from_main_greenlet(force_refresh=True) From d0719692f617899c1dd565b2ac1ced5d6ec9345c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 20 Feb 2015 03:14:57 +0100 Subject: [PATCH 0505/1650] PEP-8 Signed-off-by: Sebastian Ramacher --- bpdb/__init__.py | 3 +- bpython/curtsiesfrontend/repl.py | 110 ++++++++++++++---------- bpython/curtsiesfrontend/replpainter.py | 6 +- 3 files changed, 70 insertions(+), 49 deletions(-) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index de8be103b..9f1c57d06 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -108,6 +108,5 @@ def main(): print("Running 'cont' or 'step' will restart the program") t = sys.exc_info()[2] pdb.interaction(None, t) - print("Post mortem debugger finished. The " + mainpyfile + \ + print("Post mortem debugger finished. The " + mainpyfile + " will be restarted") - diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 02503b910..70e489ff8 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -80,6 +80,7 @@ See {example_config_url} for an example config file. Press {config.edit_config_key} to edit this config file. """ +EXAMPLE_CONFIG_URL = 'https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config' # This is needed for is_nop and should be removed once is_nop is fixed. if py3: @@ -950,8 +951,8 @@ def run_code_and_maybe_finish(self, for_code=None): if err: indent = 0 - #TODO This should be printed ABOVE the error that just happened instead - # or maybe just thrown away and not shown + # TODO This should be printed ABOVE the error that just happened + # instead or maybe just thrown away and not shown if self.current_stdouterr_line: self.display_lines.extend(paint.display_linize(self.current_stdouterr_line, self.width)) self.current_stdouterr_line = '' @@ -969,7 +970,8 @@ def keyboard_interrupt(self): self.clear_current_block(remove_from_history=False) def unhighlight_paren(self): - """modify line in self.display_buffer to unhighlight a paren if possible + """Modify line in self.display_buffer to unhighlight a paren if + possible. self.highlighted_paren should be a line in ? """ @@ -1026,7 +1028,8 @@ def send_to_stdin(self, line): # formatting, output @property def done(self): - """Whether the last block is complete - which prompt to use, ps1 or ps2""" + """Whether the last block is complete - which prompt to use, ps1 or + ps2""" return not self.buffer @property @@ -1082,7 +1085,8 @@ def display_line_with_prompt(self): def current_cursor_line_without_suggestion(self): """Current line, either output/input or Python prompt + code""" value = (self.current_output_line + - ('' if self.coderunner.running else self.display_line_with_prompt)) + ('' if self.coderunner.running else + self.display_line_with_prompt)) logger.debug('current cursor line: %r', value) return value @@ -1113,12 +1117,13 @@ def current_output_line(self, value): self.stdin.current_line = '\n' def paint(self, about_to_exit=False, user_quit=False): - """Returns an array of min_height or more rows and width columns, plus cursor position + """Returns an array of min_height or more rows and width columns, plus + cursor position - Paints the entire screen - ideally the terminal display layer will take a diff and only - write to the screen in portions that have changed, but the idea is that we don't need - to worry about that here, instead every frame is completely redrawn because - less state is cool! + Paints the entire screen - ideally the terminal display layer will take + a diff and only write to the screen in portions that have changed, but + the idea is that we don't need to worry about that here, instead every + frame is completely redrawn because less state is cool! """ # The hairiest function in the curtsies - a cleanup would be great. if about_to_exit: @@ -1127,33 +1132,41 @@ def paint(self, about_to_exit=False, user_quit=False): width, min_height = self.width, self.height show_status_bar = bool(self.status_bar.should_show_message) or self.status_bar.has_focus if show_status_bar: - min_height -= 1 # because we're going to tack the status bar on at the end, - # shoot for an array one less than the height of the screen + # because we're going to tack the status bar on at the end, shoot + # for an array one less than the height of the screen + min_height -= 1 current_line_start_row = len(self.lines_for_display) - max(0, self.scroll_offset) - #TODO how is the situation of self.scroll_offset < 0 possible? - #current_line_start_row = len(self.lines_for_display) - self.scroll_offset + # TODO how is the situation of self.scroll_offset < 0 possible? + # current_line_start_row = len(self.lines_for_display) - self.scroll_offset if self.request_paint_to_clear_screen: # or show_status_bar and about_to_exit ? self.request_paint_to_clear_screen = False arr = FSArray(min_height + current_line_start_row, width) else: arr = FSArray(0, width) - #TODO test case of current line filling up the whole screen (there aren't enough rows to show it) + # TODO test case of current line filling up the whole screen (there + # aren't enough rows to show it) - current_line = paint.paint_current_line(min_height, width, self.current_cursor_line) - # needs to happen before we calculate contents of history because calculating - # self.current_cursor_line has the side effect of unhighlighting parens in buffer + current_line = paint.paint_current_line(min_height, width, + self.current_cursor_line) + # needs to happen before we calculate contents of history because + # calculating self.current_cursor_line has the side effect of + # unhighlighting parens in buffer def move_screen_up(current_line_start_row): # move screen back up a screen minus a line while current_line_start_row < 0: - logger.debug('scroll_offset was %s, current_line_start_row was %s', self.scroll_offset, current_line_start_row) + logger.debug('scroll_offset was %s, current_line_start_row ' + 'was %s', self.scroll_offset, + current_line_start_row) self.scroll_offset = self.scroll_offset - self.height current_line_start_row = len(self.lines_for_display) - max(-1, self.scroll_offset) - logger.debug('scroll_offset changed to %s, current_line_start_row changed to %s', self.scroll_offset, current_line_start_row) + logger.debug('scroll_offset changed to %s, ' + 'current_line_start_row changed to %s', + self.scroll_offset, current_line_start_row) return current_line_start_row - if self.inconsistent_history == True and not self.history_already_messed_up: + if self.inconsistent_history and not self.history_already_messed_up: logger.debug(INCONSISTENT_HISTORY_MSG) self.history_already_messed_up = True msg = INCONSISTENT_HISTORY_MSG @@ -1165,36 +1178,41 @@ def move_screen_up(current_line_start_row): current_line_start_row = move_screen_up(current_line_start_row) logger.debug('current_line_start_row: %r', current_line_start_row) - history = paint.paint_history(max(0, current_line_start_row - 1), width, self.lines_for_display) - arr[1:history.height+1,:history.width] = history + history = paint.paint_history(max(0, current_line_start_row - 1), + width, self.lines_for_display) + arr[1:history.height + 1, :history.width] = history if arr.height <= min_height: - arr[min_height, 0] = ' ' # force scroll down to hide broken history message + arr[min_height, 0] = ' ' # force scroll down to hide broken history message - elif current_line_start_row < 0: #if current line trying to be drawn off the top of the screen + elif current_line_start_row < 0: # if current line trying to be drawn off the top of the screen logger.debug(CONTIGUITY_BROKEN_MSG) msg = CONTIGUITY_BROKEN_MSG arr[0, 0:min(len(msg), width)] = [msg[:width]] current_line_start_row = move_screen_up(current_line_start_row) - history = paint.paint_history(max(0, current_line_start_row - 1), width, self.lines_for_display) - arr[1:history.height+1,:history.width] = history + history = paint.paint_history(max(0, current_line_start_row - 1), + width, self.lines_for_display) + arr[1:history.height + 1, :history.width] = history if arr.height <= min_height: - arr[min_height, 0] = ' ' # force scroll down to hide broken history message + # force scroll down to hide broken history message + arr[min_height, 0] = ' ' else: assert current_line_start_row >= 0 - logger.debug("no history issues. start %i",current_line_start_row) - history = paint.paint_history(current_line_start_row, width, self.lines_for_display) - arr[:history.height,:history.width] = history + logger.debug("no history issues. start %i", current_line_start_row) + history = paint.paint_history(current_line_start_row, width, + self.lines_for_display) + arr[:history.height, :history.width] = history self.inconsistent_history = False if user_quit: # quit() or exit() in interp current_line_start_row = current_line_start_row - current_line.height - logger.debug("---current line row slice %r, %r", current_line_start_row, current_line_start_row + current_line.height) + logger.debug("---current line row slice %r, %r", current_line_start_row, + current_line_start_row + current_line.height) logger.debug("---current line col slice %r, %r", 0, current_line.width) arr[current_line_start_row:current_line_start_row + current_line.height, 0:current_line.width] = current_line @@ -1203,13 +1221,13 @@ def move_screen_up(current_line_start_row): return arr, (0, 0) # short circuit, no room for infobox lines = paint.display_linize(self.current_cursor_line+'X', width) - # extra character for space for the cursor + # extra character for space for the cursor current_line_end_row = current_line_start_row + len(lines) - 1 if self.stdin.has_focus: cursor_row, cursor_column = divmod(len(self.current_stdouterr_line) + self.stdin.cursor_offset, width) assert cursor_column >= 0, cursor_column - elif self.coderunner.running: #TODO does this ever happen? + elif self.coderunner.running: # TODO does this ever happen? cursor_row, cursor_column = divmod(len(self.current_cursor_line_without_suggestion) + self.cursor_offset, width) assert cursor_column >= 0, (cursor_column, len(self.current_cursor_line), len(self.current_line), self.cursor_offset) else: @@ -1259,7 +1277,6 @@ def move_screen_up(current_line_start_row): logger.debug('cursor pos: %r', (cursor_row, cursor_column)) return arr, (cursor_row, cursor_column) - @contextlib.contextmanager def in_paste_mode(self): orig_value = self.paste_mode @@ -1267,12 +1284,14 @@ def in_paste_mode(self): yield self.paste_mode = orig_value - ## Debugging shims, good example of embedding a Repl in other code + # Debugging shims, good example of embedding a Repl in other code def dumb_print_output(self): arr, cpos = self.paint() arr[cpos[0]:cpos[0]+1, cpos[1]:cpos[1]+1] = ['~'] + def my_print(msg): self.orig_stdout.write(str(msg)+'\n') + my_print('X'*(self.width+8)) my_print(' use "/" for enter '.center(self.width+8, 'X')) my_print(' use "\\" for rewind '.center(self.width+8, 'X')) @@ -1355,16 +1374,17 @@ def _set_cursor_offset(self, offset, update_completion=True, self.unhighlight_paren() cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, - "The current cursor offset from the front of the line") + "The current cursor offset from the front of the " + "line") def echo(self, msg, redraw=True): """ Notification that redrawing the current line is necessary (we don't care, since we always redraw the whole screen) - Supposed to parse and echo a formatted string with appropriate attributes. - It's not supposed to update the screen if it's reevaluating the code (as it - does with undo).""" + Supposed to parse and echo a formatted string with appropriate + attributes. It's not supposed to update the screen if it's reevaluating + the code (as it does with undo).""" logger.debug("echo called with %r" % msg) @property @@ -1438,7 +1458,7 @@ def reevaluate(self, insert_into_history=False): logger.debug('old_display_lines_offscreen %s', '|'.join(str(x) for x in old_display_lines_offscreen)) logger.debug(' display_lines_offscreen %s', '|'.join(str(x) for x in display_lines_offscreen)) if old_display_lines_offscreen[:len(display_lines_offscreen)] != display_lines_offscreen and not self.history_already_messed_up: - #self.scroll_offset = self.scroll_offset + (len(old_display_lines)-len(self.display_lines)) + # self.scroll_offset = self.scroll_offset + (len(old_display_lines)-len(self.display_lines)) self.inconsistent_history = True logger.debug('after rewind, self.inconsistent_history is %r', self.inconsistent_history) @@ -1499,7 +1519,7 @@ def version_help_text(self): return (('bpython-curtsies version %s' % bpython.__version__) + ' ' + ('using curtsies version %s' % curtsies.__version__) + '\n' + HELP_MESSAGE.format(config_file_location=default_config_path(), - example_config_url='https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config', + example_config_url=EXAMPLE_CONFIG_URL, config=self.config)) def key_help_text(self): @@ -1513,8 +1533,10 @@ def key_help_text(self): for functionality, key in [(attr[:-4].replace('_', ' '), getattr(self.config, attr)) for attr in self.config.__dict__ if attr.endswith('key')]: - if functionality in NOT_IMPLEMENTED: key = "Not Implemented" - if key == '': key = 'Disabled' + if functionality in NOT_IMPLEMENTED: + key = 'Not Implemented' + if key == '': + key = 'Disabled' pairs.append([functionality, key]) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 649a6984f..2dcbeaef0 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -28,7 +28,7 @@ def display_linize(msg, columns, blank_line=False): display_lines = ([msg[start:end] for start, end in zip( range(0, len(msg), columns), - range(columns, len(msg)+columns, columns))] + range(columns, len(msg) + columns, columns))] if msg else ([''] if blank_line else [])) return display_lines @@ -38,8 +38,8 @@ def paint_history(rows, columns, display_lines): for r, line in zip(range(rows), display_lines[-rows:]): lines.append(fmtstr(line[:columns])) r = fsarray(lines, width=columns) - assert r.shape[0] <= rows, repr(r.shape)+' '+repr(rows) - assert r.shape[1] <= columns, repr(r.shape)+' '+repr(columns) + assert r.shape[0] <= rows, repr(r.shape) + ' ' + repr(rows) + assert r.shape[1] <= columns, repr(r.shape) + ' ' + repr(columns) return r From a8cf45992e9ce555975a52aa3e36f73f832e7e43 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 20 Feb 2015 04:51:32 +0100 Subject: [PATCH 0506/1650] Fix ImportFinder/Loader for Python 2 The value of sys.meta_path varies with the Python version. Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 37 ++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 70e489ff8..ab93e0740 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -53,6 +53,9 @@ from curtsies.configfile_keynames import keymap as key_dispatch +if not py3: + import imp + logger = logging.getLogger(__name__) @@ -220,13 +223,34 @@ def __init__(self, watcher, loader): self.watcher = watcher self.loader = loader - def load_module(self, fullname): - module = self.loader.load_module(fullname) + def load_module(self, name): + module = self.loader.load_module(name) if hasattr(module, '__file__'): self.watcher.track_module(module.__file__) return module +if not py3: + class ImpImportLoader(object): + + def __init__(self, watcher, file, pathname, description): + self.watcher = watcher + self.file = file + self.pathname = pathname + self.description = description + + def load_module(self, name): + try: + module = imp.load_module(name, self.file, self.pathname, + self.description) + if hasattr(module, '__file__'): + self.watcher.track_module(module.__file__) + return module + finally: + if self.file is not None: + self.file.close() + + class ImportFinder(object): def __init__(self, watcher, old_meta_path): @@ -239,6 +263,15 @@ def find_module(self, fullname, path=None): if loader is not None: return ImportLoader(self.watcher, loader) + if not py3: + # Python 2 does not have the default finders stored in + # sys.meta_path. Use imp to perform the actual importing. + try: + result = imp.find_module(fullname, path) + return ImpImportLoader(self.watcher, *result) + except ImportError: + return None + return None From 9cc8889a2ffd1530aea59a352082a11cd8cf9f6c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 20 Feb 2015 04:51:45 +0100 Subject: [PATCH 0507/1650] Remove unnecessary else Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index ab93e0740..cc4fffc30 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -375,8 +375,7 @@ def smarter_schedule_refresh(when='now'): def smarter_request_reload(files_modified=()): if self.watching_files: request_reload(files_modified=files_modified) - else: - pass + self.request_reload = smarter_request_reload self.request_undo = request_undo self.get_term_hw = get_term_hw From adfb053cf41f6d5605c9799ddb59a927bbd41a3d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 20 Feb 2015 04:51:57 +0100 Subject: [PATCH 0508/1650] Watch multiple files in the same directory Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/filewatch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 3c44af3e1..301de9ac0 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -38,7 +38,7 @@ def _add_module(self, path): dirname = os.path.dirname(path) if dirname not in self.dirs: self.observer.schedule(self, dirname, recursive=False) - self.dirs[os.path.dirname(path)].add(path) + self.dirs[dirname].add(path) def _add_module_later(self, path): self.modules_to_add_later.append(path) From bf09b5e1e018178c9ea9dcd9c8ab24f76aa6f8d4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 20 Feb 2015 06:33:21 +0100 Subject: [PATCH 0509/1650] Use n and iteritems Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 2025c58b8..baf45530c 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -27,7 +27,7 @@ import os import rlcompleter from six.moves import range, builtins -from six import string_types +from six import string_types, iteritems from glob import glob @@ -292,10 +292,9 @@ def matches(self, cursor_offset, line, **kwargs): for word in KEYWORDS: if method_match(word, n, text): matches.add(word) - for nspace in [builtins.__dict__, locals_]: - for word, val in nspace.items(): - if (method_match(word, len(text), text) and - word != "__builtins__"): + for nspace in (builtins.__dict__, locals_): + for word, val in iteritems(nspace): + if method_match(word, n, text) and word != "__builtins__": word = try_decode(word, 'ascii') # if identifier isn't ascii, don't complete (syntax error) if word is None: From 135747196d3a29f080a50b6544ef5db2d2bf176d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 23 Feb 2015 16:32:37 +0100 Subject: [PATCH 0510/1650] Make magic methods unicode (fixes #488) Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index baf45530c..78754bb5b 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -45,7 +45,7 @@ ALL_MODES = (SIMPLE, SUBSTRING, FUZZY) -MAGIC_METHODS = tuple("__%s__" % s for s in ( +MAGIC_METHODS = tuple(u"__%s__" % s for s in ( "init", "repr", "str", "lt", "le", "eq", "ne", "gt", "ge", "cmp", "hash", "nonzero", "unicode", "getattr", "setattr", "get", "set", "call", "len", "getitem", "setitem", "iter", "reversed", "contains", "add", "sub", "mul", From d8aaad8190318eb52c540e1e49247153371d0f0b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 23 Feb 2015 17:09:51 +0100 Subject: [PATCH 0511/1650] Use unicode literals Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 78754bb5b..c6deb5fc4 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -1,3 +1,5 @@ +# coding: utf-8 + # The MIT License # # Copyright (c) 2009-2015 the bpython authors. @@ -21,6 +23,8 @@ # THE SOFTWARE. # +from __future__ import unicode_literals + import __main__ import abc import keyword @@ -45,7 +49,7 @@ ALL_MODES = (SIMPLE, SUBSTRING, FUZZY) -MAGIC_METHODS = tuple(u"__%s__" % s for s in ( +MAGIC_METHODS = tuple("__%s__" % s for s in ( "init", "repr", "str", "lt", "le", "eq", "ne", "gt", "ge", "cmp", "hash", "nonzero", "unicode", "getattr", "setattr", "get", "set", "call", "len", "getitem", "setitem", "iter", "reversed", "contains", "add", "sub", "mul", From e6b739e0d4a7debba87e816c909ba2f1059f422f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 23 Feb 2015 17:10:00 +0100 Subject: [PATCH 0512/1650] Decode attribute names Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index c6deb5fc4..09494ac49 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -342,8 +342,13 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None + start, end, word = r attrs = dir('') + if not py3: + # decode attributes + attrs = (att.decode('ascii') for att in attrs) + matches = set(att for att in attrs if att.startswith(word)) if not word.startswith('_'): return set(match for match in matches if not match.startswith('_')) From d4d96168d2107f50c07859ed98efc37e3f4f60fb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 23 Feb 2015 17:46:09 +0100 Subject: [PATCH 0513/1650] Set mod of history file to 0600 (fixes #489) Signed-off-by: Sebastian Ramacher --- bpython/history.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bpython/history.py b/bpython/history.py index d852897c8..7088a1fc2 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -26,6 +26,7 @@ from __future__ import unicode_literals import io import os +import stat from itertools import islice from six.moves import range @@ -187,6 +188,8 @@ def load_from(self, fd): return entries def save(self, filename, encoding, lines=0): + fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.TRUNC, + stat.S_IRUSR | stat.S_IWUSR) with io.open(filename, 'w', encoding=encoding, errors='ignore') as hfile: with FileLock(hfile): @@ -204,7 +207,9 @@ def append_reload_and_write(self, s, filename, encoding): return self.append(s) try: - with io.open(filename, 'a+', encoding=encoding, + fd = os.open(filename, os.O_APPEND | os.O_RDWR | os.O_CREAT, + stat.S_IRUSR | stat.S_IWUSR) + with io.open(fd, 'a+', encoding=encoding, errors='ignore') as hfile: with FileLock(hfile): # read entries From 5880f85d87498c4ebd65ca6d18d43196845dee08 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 23 Feb 2015 17:55:36 +0100 Subject: [PATCH 0514/1650] Update changelog Signed-off-by: Sebastian Ramacher --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 1325baa9f..43e26dd2c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ Changelog New features: +* #425: Added curtsies 0.2.x support. Fixes: From 846ca4dea9deccc4f2cc5e3167a9e37de2611493 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 24 Feb 2015 14:17:16 +0100 Subject: [PATCH 0515/1650] Be more careful with glob.glob (fixes #491) If we have glob.escape, use it. Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 18 +++++++++++++++--- bpython/test/test_autocomplete.py | 10 +++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 09494ac49..799ea261e 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -27,14 +27,15 @@ import __main__ import abc +import glob import keyword import os +import re import rlcompleter +import sys from six.moves import range, builtins from six import string_types, iteritems -from glob import glob - from bpython import inspection from bpython import importcompletion from bpython import line as lineparts @@ -160,6 +161,17 @@ class FilenameCompletion(BaseCompletionType): def __init__(self): super(FilenameCompletion, self).__init__(False) + if sys.version_info[:2] >= (3, 4): + def safe_glob(self, pathname): + return glob.glob(glob.escape(pathname) + '*') + else: + def safe_glob(self, pathname): + try: + return glob.glob(pathname + '*') + except re.error: + # see #491 + return tuple() + def matches(self, cursor_offset, line, **kwargs): cs = lineparts.current_string(cursor_offset, line) if cs is None: @@ -168,7 +180,7 @@ def matches(self, cursor_offset, line, **kwargs): matches = set() username = text.split(os.path.sep, 1)[0] user_dir = os.path.expanduser(username) - for filename in glob(os.path.expanduser(text + '*')): + for filename in self.safe_glob(os.path.expanduser(text)): if os.path.isdir(filename): filename += os.path.sep if text.startswith('~'): diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index b262990b2..db721ceee 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -123,15 +123,15 @@ def test_locate_fails_when_not_in_string(self): def test_locate_succeeds_when_in_string(self): self.assertEqual(self.completer.locate(4, "a'bc'd"), (2, 4, 'bc')) - @mock.patch('bpython.autocomplete.glob', new=lambda text: []) + @mock.patch('glob.glob', new=lambda text: []) def test_match_returns_none_if_not_in_string(self): self.assertEqual(self.completer.matches(2, 'abcd'), None) - @mock.patch('bpython.autocomplete.glob', new=lambda text: []) + @mock.patch('glob.glob', new=lambda text: []) def test_match_returns_empty_list_when_no_files(self): self.assertEqual(self.completer.matches(2, '"a'), set()) - @mock.patch('bpython.autocomplete.glob', + @mock.patch('glob.glob', new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: False) @@ -140,7 +140,7 @@ def test_match_returns_files_when_files_exist(self): self.assertEqual(sorted(self.completer.matches(2, '"x')), ['aaaaa', 'abcde']) - @mock.patch('bpython.autocomplete.glob', + @mock.patch('glob.glob', new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: True) @@ -149,7 +149,7 @@ def test_match_returns_dirs_when_dirs_exist(self): self.assertEqual(sorted(self.completer.matches(2, '"x')), ['aaaaa/', 'abcde/']) - @mock.patch('bpython.autocomplete.glob', + @mock.patch('glob.glob', new=lambda text: ['/expand/ed/abcde', '/expand/ed/aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text.replace('~', '/expand/ed')) From 601df715e0d062050c8a4687aaebce03aed7b4f5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 24 Feb 2015 14:24:58 +0100 Subject: [PATCH 0516/1650] Use glob.iglob Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 4 ++-- bpython/test/test_autocomplete.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 799ea261e..f579d91e7 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -163,11 +163,11 @@ def __init__(self): if sys.version_info[:2] >= (3, 4): def safe_glob(self, pathname): - return glob.glob(glob.escape(pathname) + '*') + return glob.iglob(glob.escape(pathname) + '*') else: def safe_glob(self, pathname): try: - return glob.glob(pathname + '*') + return glob.iglob(pathname + '*') except re.error: # see #491 return tuple() diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index db721ceee..9db0c5074 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -123,15 +123,15 @@ def test_locate_fails_when_not_in_string(self): def test_locate_succeeds_when_in_string(self): self.assertEqual(self.completer.locate(4, "a'bc'd"), (2, 4, 'bc')) - @mock.patch('glob.glob', new=lambda text: []) + @mock.patch('glob.iglob', new=lambda text: []) def test_match_returns_none_if_not_in_string(self): self.assertEqual(self.completer.matches(2, 'abcd'), None) - @mock.patch('glob.glob', new=lambda text: []) + @mock.patch('glob.iglob', new=lambda text: []) def test_match_returns_empty_list_when_no_files(self): self.assertEqual(self.completer.matches(2, '"a'), set()) - @mock.patch('glob.glob', + @mock.patch('glob.iglob', new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: False) @@ -140,7 +140,7 @@ def test_match_returns_files_when_files_exist(self): self.assertEqual(sorted(self.completer.matches(2, '"x')), ['aaaaa', 'abcde']) - @mock.patch('glob.glob', + @mock.patch('glob.iglob', new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: True) @@ -149,7 +149,7 @@ def test_match_returns_dirs_when_dirs_exist(self): self.assertEqual(sorted(self.completer.matches(2, '"x')), ['aaaaa/', 'abcde/']) - @mock.patch('glob.glob', + @mock.patch('glob.iglob', new=lambda text: ['/expand/ed/abcde', '/expand/ed/aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text.replace('~', '/expand/ed')) From 77506c1208febc0f93b05f9aff07b8f8d6ed5ae8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 25 Feb 2015 15:51:01 +0100 Subject: [PATCH 0517/1650] Only use iglob if we really can Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index f579d91e7..6e3ddb900 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -167,7 +167,7 @@ def safe_glob(self, pathname): else: def safe_glob(self, pathname): try: - return glob.iglob(pathname + '*') + return glob.glob(pathname + '*') except re.error: # see #491 return tuple() From 45fb40dc0bf264df0506c888ef5e32f8da050b90 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 25 Feb 2015 16:10:29 +0100 Subject: [PATCH 0518/1650] Patch correct function Signed-off-by: Sebastian Ramacher --- bpython/test/test_autocomplete.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 9db0c5074..165965a10 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -3,7 +3,7 @@ from collections import namedtuple import inspect import keyword -from bpython._py3compat import py3 +import sys try: import unittest2 as unittest @@ -17,8 +17,14 @@ has_jedi = False from bpython import autocomplete +from bpython._py3compat import py3 from bpython.test import mock +if sys.version_info[:2] >= (3, 4): + glob_function = 'glob.iglob' +else: + glob_function = 'glob.glob' + class TestSafeEval(unittest.TestCase): def test_catches_syntax_error(self): @@ -123,16 +129,15 @@ def test_locate_fails_when_not_in_string(self): def test_locate_succeeds_when_in_string(self): self.assertEqual(self.completer.locate(4, "a'bc'd"), (2, 4, 'bc')) - @mock.patch('glob.iglob', new=lambda text: []) + @mock.patch(glob_function, new=lambda text: []) def test_match_returns_none_if_not_in_string(self): self.assertEqual(self.completer.matches(2, 'abcd'), None) - @mock.patch('glob.iglob', new=lambda text: []) + @mock.patch(glob_function, new=lambda text: []) def test_match_returns_empty_list_when_no_files(self): self.assertEqual(self.completer.matches(2, '"a'), set()) - @mock.patch('glob.iglob', - new=lambda text: ['abcde', 'aaaaa']) + @mock.patch(glob_function, new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: False) @mock.patch('os.path.sep', new='/') @@ -140,8 +145,7 @@ def test_match_returns_files_when_files_exist(self): self.assertEqual(sorted(self.completer.matches(2, '"x')), ['aaaaa', 'abcde']) - @mock.patch('glob.iglob', - new=lambda text: ['abcde', 'aaaaa']) + @mock.patch(glob_function, new=lambda text: ['abcde', 'aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text) @mock.patch('os.path.isdir', new=lambda text: True) @mock.patch('os.path.sep', new='/') @@ -149,7 +153,7 @@ def test_match_returns_dirs_when_dirs_exist(self): self.assertEqual(sorted(self.completer.matches(2, '"x')), ['aaaaa/', 'abcde/']) - @mock.patch('glob.iglob', + @mock.patch(glob_function, new=lambda text: ['/expand/ed/abcde', '/expand/ed/aaaaa']) @mock.patch('os.path.expanduser', new=lambda text: text.replace('~', '/expand/ed')) From 5f836ead89506c4a67b1c3c2fc7f4b98d823041c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 25 Feb 2015 16:24:07 +0100 Subject: [PATCH 0519/1650] Add a test for #491 Signed-off-by: Sebastian Ramacher --- bpython/test/test_autocomplete.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 165965a10..cad960e33 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -129,6 +129,9 @@ def test_locate_fails_when_not_in_string(self): def test_locate_succeeds_when_in_string(self): self.assertEqual(self.completer.locate(4, "a'bc'd"), (2, 4, 'bc')) + def test_issue_491(self): + self.assertNotEqual(self.completer.matches(9, '"a[a.l-1]'), None) + @mock.patch(glob_function, new=lambda text: []) def test_match_returns_none_if_not_in_string(self): self.assertEqual(self.completer.matches(2, 'abcd'), None) From 62eccc7527422e6cfd50e5e979897338b46e8927 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 Feb 2015 12:52:47 +0100 Subject: [PATCH 0520/1650] Remove curtsies restriction Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.install.sh b/.travis.install.sh index 77887e702..9ab17068f 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -8,7 +8,7 @@ if [[ $RUN == nosetests ]]; then # core dependencies pip install pygments pip install requests - pip install 'curtsies >=0.1.17,<0.2.0' + pip install 'curtsies >=0.1.17' pip install greenlet pip install 'six >=1.4' # filewatch specific dependencies From 934c5b64f009dc5665a1f925051190017beb91c3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 Feb 2015 12:53:35 +0100 Subject: [PATCH 0521/1650] Bump six dependency (fixes #494) from six.moves.X import Y only works with six 1.5 and newer. Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index 9ab17068f..dadbd2373 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -10,7 +10,7 @@ if [[ $RUN == nosetests ]]; then pip install requests pip install 'curtsies >=0.1.17' pip install greenlet - pip install 'six >=1.4' + pip install 'six >=1.5' # filewatch specific dependencies pip install watchdog # jedi specific dependencies diff --git a/setup.py b/setup.py index 22ca6e9f5..91c7d260b 100755 --- a/setup.py +++ b/setup.py @@ -212,7 +212,7 @@ def initialize_options(self): 'requests', 'curtsies >=0.1.18', 'greenlet', - 'six >=1.4' + 'six >=1.5' ] extras_require = { From 2b32c81b6871fffbe414079c59e9854d2369747c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 Feb 2015 18:08:46 +0100 Subject: [PATCH 0522/1650] Open the FD Signed-off-by: Sebastian Ramacher --- bpython/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/history.py b/bpython/history.py index 7088a1fc2..e08ec2684 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -190,7 +190,7 @@ def load_from(self, fd): def save(self, filename, encoding, lines=0): fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.TRUNC, stat.S_IRUSR | stat.S_IWUSR) - with io.open(filename, 'w', encoding=encoding, + with io.open(fd, 'w', encoding=encoding, errors='ignore') as hfile: with FileLock(hfile): self.save_to(hfile, self.entries, lines) From f4087c246236f24d37e0b1870c2371a81f471387 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 Feb 2015 21:06:55 +0100 Subject: [PATCH 0523/1650] Remove curtsies restrictions Signed-off-by: Sebastian Ramacher --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index bf7a26978..08f9eef15 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ Dependencies * Pygments * requests -* curtsies >= 0.1.18,<0.2.0 +* curtsies >= 0.1.18 * greenlet * Sphinx != 1.1.2 (optional, for the documentation) * mock (optional, for the testsuite) From 886e8e03baeacf6c9c92cc5a364708c4219a839c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 Feb 2015 21:07:31 +0100 Subject: [PATCH 0524/1650] Fix conditional dependency on libs required for SNI And replace them with requests[security] Signed-off-by: Sebastian Ramacher --- README.rst | 8 +++----- setup.py | 9 +++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 08f9eef15..bf5fb6e71 100644 --- a/README.rst +++ b/README.rst @@ -18,12 +18,10 @@ Dependencies * watchdog (optional, for monitoring imported modules for changes) * jedi (optional, for experimental multiline completion) -If you are using Python 2 on Mac OS X, the following dependencies are required -as well: +If you are using Python 2 before 2.7.7, the following dependency is also +required: -* pyOpenSSL -* ndg-httpsclien -* pyasn1 +* requests[security] If you have problems installing cffi which is needed by pyOpenSSL, please take a look at https://cffi.readthedocs.org/en/release-0.8/#macos-x. diff --git a/setup.py b/setup.py index 91c7d260b..44ea3d3bf 100755 --- a/setup.py +++ b/setup.py @@ -219,12 +219,9 @@ def initialize_options(self): 'urwid': ['urwid'], 'watch': ['watchdog'], 'jedi': ['jedi'], - # need PyOpenSSL for SNI support (only 2.X and on Darwin) - # list of packages taken from - # https://github.com/kennethreitz/requests/blob/master/requests/packages/urllib3/contrib/pyopenssl.py - ':sys_platform == "darwin" and '\ - '(python_version == "2.6" or python_version == "2.7")': [ - 'PyOpenSSL', 'ndg-httpsclient', 'pyasn1' + # need requests[security] for SNI support (only before 2.7.7) + ':python_full_version <= "2.7.7"': [ + 'requests[security]' ] } From 1caf2430d1352a7b2645f203fc9ac252c9836f21 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 Feb 2015 21:51:19 +0100 Subject: [PATCH 0525/1650] Fix environment marker This is stupid. PEP-426 mentions < but it does not seem to work. Signed-off-by: Sebastian Ramacher --- setup.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 44ea3d3bf..88c7f7a2b 100755 --- a/setup.py +++ b/setup.py @@ -220,7 +220,14 @@ def initialize_options(self): 'watch': ['watchdog'], 'jedi': ['jedi'], # need requests[security] for SNI support (only before 2.7.7) - ':python_full_version <= "2.7.7"': [ + ':python_version == "2.6" or ' + 'python_full_version == "2.7.0" or ' \ + 'python_full_version == "2.7.1" or ' \ + 'python_full_version == "2.7.2" or ' \ + 'python_full_version == "2.7.3" or ' \ + 'python_full_version == "2.7.4" or ' \ + 'python_full_version == "2.7.5" or ' \ + 'python_full_version == "2.7.6"': [ 'requests[security]' ] } From e77ce898fac4093e91ebbbca6cc2c60c45781b34 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 Feb 2015 22:07:39 +0100 Subject: [PATCH 0526/1650] Strip version correctly Signed-off-by: Sebastian Ramacher --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 88c7f7a2b..e34a95981 100755 --- a/setup.py +++ b/setup.py @@ -102,6 +102,7 @@ def git_describe_to_python_version(version): # get version from existing version file with open(version_file) as vf: version = vf.read().strip().split('=')[-1].replace('\'', '') + version = version.strip() except IOError: pass From 97f0f45d903d0770d3fee50568429dd67214c90f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 2 Mar 2015 13:15:50 -0500 Subject: [PATCH 0527/1650] failing test for #497 in Python 3 --- bpython/test/test_repl.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index c276710b0..3e2d2831c 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,7 +1,9 @@ import collections from itertools import islice import os +import shutil import socket +import tempfile from six.moves import range try: @@ -11,7 +13,7 @@ from bpython._py3compat import py3 from bpython import config, repl, cli, autocomplete -from bpython.test import MagicIterMock, mock +from bpython.test import MagicIterMock, mock, FixLanguageTestCase as TestCase def setup_config(conf): @@ -252,6 +254,25 @@ def test_current_line(self): # TODO add tests for various failures without using current function +class TestEditConfig(TestCase): + def setUp(self): + self.repl = FakeRepl() + self.repl.interact.confirm = lambda msg: True + self.repl.interact.notify = lambda msg: None + self.repl.config.editor = 'true' + + def test_create_config(self): + tmp_dir = tempfile.mkdtemp() + try: + config_path = os.path.join(tmp_dir, 'newdir', 'config') + self.repl.config.config_path = config_path + self.repl.edit_config() + self.assertTrue(os.path.exists(config_path)) + finally: + shutil.rmtree(tmp_dir) + self.assertFalse(os.path.exists(config_path)) + + class TestRepl(unittest.TestCase): def set_input_line(self, line): From 83ce5e9371cd4973cb20d38b502ebf73c64df2c9 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 2 Mar 2015 13:16:32 -0500 Subject: [PATCH 0528/1650] write unicode in Python 3 --- bpython/repl.py | 2 ++ bpython/sample-config | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index d3c2fd9f6..e84aa3ae8 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1070,6 +1070,8 @@ def edit_config(self): try: default_config = pkgutil.get_data('bpython', 'sample-config') + if py3: # py3 files need unicode + default_config = default_config.decode('ascii') bpython_dir, script_name = os.path.split(__file__) containing_dir = os.path.dirname( os.path.abspath(self.config.config_path)) diff --git a/bpython/sample-config b/bpython/sample-config index 3fe3f2f87..be7d3636e 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -45,7 +45,7 @@ # save_append_py = False # The name of a helper executable that should perform pastebin upload on -# bpython’s behalf. If unset, bpython uploads pastes to bpaste.net. (default: ) +# bpython's behalf. If unset, bpython uploads pastes to bpaste.net. (default: ) #pastebin_helper = gist.py # How long an undo must be expected to take before prompting for how From 85e63b4b6fb6ac00fa9154f103dc16530bd3c09e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Mar 2015 22:46:10 +0100 Subject: [PATCH 0529/1650] Include 0.14.1 CHANGELOG All fixes are also in master. Signed-off-by: Sebastian Ramacher --- CHANGELOG | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 43e26dd2c..0f229b3d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,17 @@ Fixes: * #484: Switch `bpython.embed` to the curtsies frontend. +0.14.1 +------ + +Fixes: + +* #483: Fixed jedi exceptions handling. +* #486: Fixed Python 3.3 compatibility. +* #489: Create history file with mode 0600. +* #491: Fix issues with file name completion. +* #494: Fix six version requirement. +* Fix conditional dependencies for SNI support in Python versions before 2.7.7. 0.14 ---- From a9faa967804a4b05316c5fd38477bea295c746e2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 3 Mar 2015 23:12:55 +0100 Subject: [PATCH 0530/1650] Make KeyMap a new style class Signed-off-by: Sebastian Ramacher --- bpython/keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/keys.py b/bpython/keys.py index 3e0d7288b..b026e70b8 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -25,7 +25,7 @@ from six.moves import range -class KeyMap: +class KeyMap(object): def __init__(self, default=''): self.map = {} From 95c05bcfe8695e8173084d5b3def9d769f851d37 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 3 Mar 2015 23:21:10 +0100 Subject: [PATCH 0531/1650] Remove useless use of str Signed-off-by: Sebastian Ramacher --- bpython/keys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/keys.py b/bpython/keys.py index b026e70b8..619116a19 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -69,7 +69,7 @@ def __setitem__(self, key, value): # fill dispatch with function keys for x in range(1, 13): - cli_key_dispatch['F%s' % str(x)] = ('KEY_F(%s)' % str(x),) + cli_key_dispatch['F%d' % x] = ('KEY_F(%d)' % x,) for x in range(1, 13): - urwid_key_dispatch['F%s' % str(x)] = 'f%s' % str(x) + urwid_key_dispatch['F%d' % x] = 'f%d' % x From 907473bffc6828ab4c62f8027276060f1d759e76 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 3 Mar 2015 23:32:15 +0100 Subject: [PATCH 0532/1650] use unicode literals Signed-off-by: Sebastian Ramacher --- bpython/config.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 13c7a880c..1ebe68a0b 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -1,5 +1,7 @@ # encoding: utf-8 +from __future__ import unicode_literals + import os import sys import locale @@ -277,23 +279,23 @@ def get_key_no_doublebind(command): # set box drawing characters if config.getboolean('general', 'unicode_box') and supports_box_chars(): - struct.left_border = u'│' - struct.right_border = u'│' - struct.top_border = u'─' - struct.bottom_border = u'─' - struct.left_bottom_corner = u'└' - struct.right_bottom_corner = u'┘' - struct.left_top_corner = u'┌' - struct.right_top_corner = u'┐' + struct.left_border = '│' + struct.right_border = '│' + struct.top_border = '─' + struct.bottom_border = '─' + struct.left_bottom_corner = '└' + struct.right_bottom_corner = '┘' + struct.left_top_corner = '┌' + struct.right_top_corner = '┐' else: - struct.left_border = u'|' - struct.right_border = u'|' - struct.top_border = u'-' - struct.bottom_border = u'-' - struct.left_bottom_corner = u'+' - struct.right_bottom_corner = u'+' - struct.left_top_corner = u'+' - struct.right_top_corner = u'+' + struct.left_border = '|' + struct.right_border = '|' + struct.top_border = '-' + struct.bottom_border = '-' + struct.left_bottom_corner = '+' + struct.right_bottom_corner = '+' + struct.left_top_corner = '+' + struct.right_top_corner = '+' def load_theme(struct, path, colors, default_colors): From f192efa07f9475ae210b28112683173242695d16 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Mar 2015 00:13:29 +0100 Subject: [PATCH 0533/1650] Document LazyReCompile Signed-off-by: Sebastian Ramacher --- bpython/lazyre.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bpython/lazyre.py b/bpython/lazyre.py index f2b29c574..e27b8de5f 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -24,6 +24,10 @@ class LazyReCompile(object): + """Compile regular expressions on first use + + This class allows to store regular expressions and compiles them on first + use.""" def __init__(self, regex, flags=0): self.regex = regex From 0f92c18fa5023840fb931a47cc86c22858ca71e0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 7 Mar 2015 14:09:30 +0100 Subject: [PATCH 0534/1650] Use pkgutil.ImpLoader for the import loader Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index cc4fffc30..2605f5ff8 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -55,6 +55,7 @@ if not py3: import imp + import pkgutil logger = logging.getLogger(__name__) @@ -231,24 +232,18 @@ def load_module(self, name): if not py3: - class ImpImportLoader(object): + # Remember that pkgutil.ImpLoader is an old style class. + class ImpImportLoader(pkgutil.ImpLoader): - def __init__(self, watcher, file, pathname, description): + def __init__(self, watcher, *args): self.watcher = watcher - self.file = file - self.pathname = pathname - self.description = description + pkgutil.ImpLoader.__init__(self, *args) def load_module(self, name): - try: - module = imp.load_module(name, self.file, self.pathname, - self.description) - if hasattr(module, '__file__'): - self.watcher.track_module(module.__file__) - return module - finally: - if self.file is not None: - self.file.close() + module = pkgutil.ImpLoader.load_module(self, name) + if hasattr(module, '__file__'): + self.watcher.track_module(module.__file__) + return module class ImportFinder(object): @@ -268,7 +263,7 @@ def find_module(self, fullname, path=None): # sys.meta_path. Use imp to perform the actual importing. try: result = imp.find_module(fullname, path) - return ImpImportLoader(self.watcher, *result) + return ImpImportLoader(self.watcher, fullname, *result) except ImportError: return None From 4b9a3283b39874fa1ec9a4fd50dbe3975f26b448 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 27 Feb 2015 08:30:03 -0500 Subject: [PATCH 0535/1650] new global in pypy, sequenceiterator --- bpython/test/test_curtsies_painting.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 59c910d4f..005a57cc4 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -89,18 +89,18 @@ def test_run_line(self): def test_completion(self): self.repl.height, self.repl.width = (5, 32) - self.repl.current_line = 'se' + self.repl.current_line = 'an' self.cursor_offset = 2 if config.supports_box_chars(): - screen = ['>>> se', + screen = ['>>> an', '┌───────────────────────┐', - '│ set( setattr( │', + '│ and any( │', '└───────────────────────┘', 'Welcome to bpython! Press f'] else: - screen = ['>>> se', + screen = ['>>> an', '+-----------------------+', - '| set( setattr( |', + '| and any( |', '+-----------------------+', 'Welcome to bpython! Press f'] self.assert_paint_ignoring_formatting(screen, (0, 4)) From 0662921cc4bb7807b32b7b8c73cab57fd72af538 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 27 Feb 2015 08:30:48 -0500 Subject: [PATCH 0536/1650] Extra enter not required in pypy --- bpython/test/test_curtsies_painting.py | 50 +++++++++++++++----------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 005a57cc4..7a378a9e9 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -284,12 +284,14 @@ def test_rewind_inconsistent_history_more_lines_same_screen(self): self.repl.width = 60 sys.a = 5 self.enter('import sys') - self.enter('for i in range(sys.a): print(sys.a)') - self.enter() + self.enter('for i in range(sys.a):') + self.enter(' print(sys.a)') + self.enter('') self.enter('1 + 1') self.enter('2 + 2') screen = ['>>> import sys', - '>>> for i in range(sys.a): print(sys.a)', + '>>> for i in range(sys.a):', + '... print(sys.a)', '... ', '5', '5', @@ -301,9 +303,9 @@ def test_rewind_inconsistent_history_more_lines_same_screen(self): '>>> 2 + 2', '4', '>>> '] - self.assert_paint_ignoring_formatting(screen, (12, 4)) + self.assert_paint_ignoring_formatting(screen, (13, 4)) self.repl.scroll_offset += len(screen) - self.repl.height - self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) + self.assert_paint_ignoring_formatting(screen[9:], (4, 4)) sys.a = 6 self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], @@ -322,12 +324,14 @@ def test_rewind_inconsistent_history_more_lines_lower_screen(self): self.repl.width = 60 sys.a = 5 self.enter("import sys") - self.enter("for i in range(sys.a): print(sys.a)") - self.enter() + self.enter("for i in range(sys.a):") + self.enter(" print(sys.a)") + self.enter("") self.enter("1 + 1") self.enter("2 + 2") screen = [">>> import sys", - ">>> for i in range(sys.a): print(sys.a)", + ">>> for i in range(sys.a):", + "... print(sys.a)", '... ', '5', '5', @@ -339,9 +343,9 @@ def test_rewind_inconsistent_history_more_lines_lower_screen(self): '>>> 2 + 2', '4', '>>> '] - self.assert_paint_ignoring_formatting(screen, (12, 4)) + self.assert_paint_ignoring_formatting(screen, (13, 4)) self.repl.scroll_offset += len(screen) - self.repl.height - self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) + self.assert_paint_ignoring_formatting(screen[9:], (4, 4)) sys.a = 8 self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], @@ -359,12 +363,14 @@ def test_rewind_inconsistent_history_more_lines_raise_screen(self): self.repl.width = 60 sys.a = 5 self.enter("import sys") - self.enter("for i in range(sys.a): print(sys.a)") - self.enter() + self.enter("for i in range(sys.a):") + self.enter(" print(sys.a)") + self.enter("") self.enter("1 + 1") self.enter("2 + 2") screen = [">>> import sys", - ">>> for i in range(sys.a): print(sys.a)", + ">>> for i in range(sys.a):", + "... print(sys.a)", '... ', '5', '5', @@ -376,9 +382,9 @@ def test_rewind_inconsistent_history_more_lines_raise_screen(self): '>>> 2 + 2', '4', '>>> '] - self.assert_paint_ignoring_formatting(screen, (12, 4)) + self.assert_paint_ignoring_formatting(screen, (13, 4)) self.repl.scroll_offset += len(screen) - self.repl.height - self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) + self.assert_paint_ignoring_formatting(screen[9:], (4, 4)) sys.a = 1 self.undo() screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], @@ -394,12 +400,14 @@ def test_rewind_inconsistent_history_more_lines_raise_screen(self): def test_rewind_history_not_quite_inconsistent(self): self.repl.width = 50 sys.a = 5 - self.enter("for i in range(__import__('sys').a): print(i)") - self.enter() + self.enter("for i in range(__import__('sys').a):") + self.enter(" print(i)") + self.enter("") self.enter("1 + 1") self.enter("2 + 2") - screen = [">>> for i in range(__import__('sys').a): print(i)", - '... ', + screen = [">>> for i in range(__import__('sys').a):", + "... print(i)", + "... ", '0', '1', '2', @@ -410,9 +418,9 @@ def test_rewind_history_not_quite_inconsistent(self): '>>> 2 + 2', '4', '>>> '] - self.assert_paint_ignoring_formatting(screen, (11, 4)) + self.assert_paint_ignoring_formatting(screen, (12, 4)) self.repl.scroll_offset += len(screen) - self.repl.height - self.assert_paint_ignoring_formatting(screen[7:], (4, 4)) + self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) sys.a = 6 self.undo() screen = ['5', From 4408f9dccb9b65265a71aa706293711b6835fe3d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 27 Feb 2015 08:31:03 -0500 Subject: [PATCH 0537/1650] prettier errors with multiline string assert --- bpython/test/test_interpreter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 3474ca5fc..7c05a7f0b 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -29,7 +29,7 @@ def append_to_a(message): bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + \ bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n' - self.assertEquals(str(plain('').join(a)), str(expected)) + self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) def test_traceback(self): @@ -54,7 +54,7 @@ def g(): cyan('') + '\n' + bold(red('NameError')) + ': ' + \ cyan("name 'g' is not defined") + '\n' - self.assertEquals(str(plain('').join(a)), str(expected)) + self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) @unittest.skipIf(py3, "runsource() accepts only unicode in Python 3") From 9bc21e59d30f2987153b1e53f4843d17ca78bf29 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 7 Mar 2015 08:41:24 -0500 Subject: [PATCH 0538/1650] specialize source finding error messages --- bpython/test/test_interpreter.py | 22 ++++++++++++++++++---- bpython/test/test_repl.py | 23 +++++++++++++++-------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 7c05a7f0b..a203accbc 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -2,6 +2,8 @@ from __future__ import unicode_literals +import sys + try: import unittest2 as unittest except ImportError: @@ -13,6 +15,8 @@ from bpython._py3compat import py3 from bpython.test import mock +pypy = 'PyPy' in sys.version + class TestInterpreter(unittest.TestCase): def test_syntaxerror(self): @@ -25,9 +29,14 @@ def append_to_a(message): i.write = append_to_a i.runsource('1.1.1.1') - expected = ' File ' + green('""') + ', line ' + \ - bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + \ - bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n' + if pypy: + expected = ' File ' + green('""') + ', line ' + \ + bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + \ + bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n' + else: + expected = ' File ' + green('""') + ', line ' + \ + bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + \ + bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n' self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) @@ -49,10 +58,15 @@ def g(): i.runsource('g()') + if pypy: + global_not_found = "global name 'g' is not defined" + else: + global_not_found = "name 'g' is not defined" + expected = 'Traceback (most recent call last):\n File ' + \ green('""') + ', line ' + bold(magenta('1')) + ', in ' + \ cyan('') + '\n' + bold(red('NameError')) + ': ' + \ - cyan("name 'g' is not defined") + '\n' + cyan(global_not_found) + '\n' self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 3e2d2831c..f4f8e6e7d 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,10 +1,12 @@ -import collections from itertools import islice +import collections +import inspect import os import shutil import socket import tempfile from six.moves import range +import sys try: import unittest2 as unittest @@ -15,6 +17,7 @@ from bpython import config, repl, cli, autocomplete from bpython.test import MagicIterMock, mock, FixLanguageTestCase as TestCase +pypy = 'PyPy' in sys.version def setup_config(conf): config_struct = config.Struct() @@ -230,21 +233,25 @@ def assert_get_source_error_for_current_function(self, func, msg): def test_current_function(self): self.set_input_line('INPUTLINE') - self.repl.current_func = collections.MutableSet.add - self.assertIn("Add an element.", + self.repl.current_func = inspect.getsource + self.assertIn("text of the source code", self.repl.get_source_of_current_name()) self.assert_get_source_error_for_current_function( - collections.defaultdict.copy, "No source code found for INPUTLINE") + [], "No source code found for INPUTLINE") self.assert_get_source_error_for_current_function( - collections.defaultdict, "could not find class definition") + list.pop, "No source code found for INPUTLINE") + @unittest.skipIf(pypy, 'different errors for PyPy') + def test_current_function_cpython(self): + self.set_input_line('INPUTLINE') self.assert_get_source_error_for_current_function( - [], "No source code found for INPUTLINE") - + collections.defaultdict.copy, "No source code found for INPUTLINE") self.assert_get_source_error_for_current_function( - list.pop, "No source code found for INPUTLINE") + collections.defaultdict, "could not find class definition") + + def test_current_line(self): self.repl.interp.locals['a'] = socket.socket From 07d8d53f06863d051457f2f8845ad409e3b12828 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 6 Mar 2015 21:53:55 -0500 Subject: [PATCH 0539/1650] Fix pkgutil.get_data for sample-config --- bpython/curtsiesfrontend/repl.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2605f5ff8..477bdf095 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -245,6 +245,9 @@ def load_module(self, name): self.watcher.track_module(module.__file__) return module + def get_data(self, pathname): + return open(pathname, "rb").read() + class ImportFinder(object): From 76892cae3f3aea1258e7f38055fd55314d44ae14 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 6 Mar 2015 13:35:18 -0500 Subject: [PATCH 0540/1650] add keys --- bpython/config.py | 5 +++++ bpython/curtsiesfrontend/repl.py | 4 ++-- bpython/sample-config | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 1ebe68a0b..3fb8ff229 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -107,10 +107,12 @@ def loadini(struct, configfile): 'exit': '', 'external_editor': 'F7', 'help': 'F1', + 'incremental_search': 'M-s', 'last_output': 'F9', 'left': 'C-b', 'pastebin': 'F8', 'reimport': 'F6', + 'reverse_incremental_search': 'M-r', 'right': 'C-f', 'save': 'C-s', 'search': 'C-o', @@ -185,6 +187,9 @@ def get_key_no_doublebind(command): struct.toggle_file_watch_key = get_key_no_doublebind('toggle_file_watch') struct.undo_key = get_key_no_doublebind('undo') struct.reimport_key = get_key_no_doublebind('reimport') + struct.reverse_incremental_search_key = get_key_no_doublebind( + 'reverse_incremental_search') + struct.incremental_search_key = get_key_no_doublebind('incremental_search') struct.up_one_line_key = get_key_no_doublebind('up_one_line') struct.down_one_line_key = get_key_no_doublebind('down_one_line') struct.cut_to_buffer_key = get_key_no_doublebind('cut_to_buffer') diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 477bdf095..85c82f9d5 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -611,9 +611,9 @@ def process_key_event(self, e): self.on_control_d() elif e in ("",): self.get_last_word() - elif e in ("",): + elif e in key_dispatch[self.config.reverse_incremental_search_key]: self.incremental_search(reverse=True) - elif e in ("",): + elif e in key_dispatch[self.config.incremental_search_key]: self.incremental_search() elif (e in ("",) + key_dispatch[self.config.backspace_key] and self.incremental_search_mode): diff --git a/bpython/sample-config b/bpython/sample-config index be7d3636e..7fdf96361 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -77,6 +77,8 @@ # exit = C-d # external_editor = F7 # edit_config = F3 +# reverse_incremental_search = M-r +# incremental_search = M-s [curtsies] From 9f0d07378a2d97aa439954d3e5309a93e67a387d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 7 Mar 2015 00:20:48 -0500 Subject: [PATCH 0541/1650] remove duplicate keybind commands --- bpython/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 3fb8ff229..3b3f90e14 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -205,8 +205,6 @@ def get_key_no_doublebind(command): struct.end_of_line_key = get_key_no_doublebind('end_of_line') struct.beginning_of_line_key = get_key_no_doublebind('beginning_of_line') struct.transpose_chars_key = get_key_no_doublebind('transpose_chars') - struct.clear_line_key = get_key_no_doublebind('clear_line') - struct.clear_screen_key = get_key_no_doublebind('clear_screen') struct.exit_key = get_key_no_doublebind('exit') struct.last_output_key = get_key_no_doublebind('last_output') struct.edit_config_key = get_key_no_doublebind('edit_config') From 1f2e808ea29ecb1ae551dfca305783f26416bf65 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 7 Mar 2015 00:21:38 -0500 Subject: [PATCH 0542/1650] remove outdated, limited check for vaild config --- bpython/config.py | 4 ---- bpython/curtsiesfrontend/manual_readline.py | 8 ++------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 3b3f90e14..fd613f46c 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -269,10 +269,6 @@ def get_key_no_doublebind(command): (color_scheme_name, )) sys.exit(1) - # checks for valid key configuration this part still sucks - for key in (struct.pastebin_key, struct.save_key): - key_dispatch[key] - # expand path of history file struct.hist_file = os.path.expanduser(struct.hist_file) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 6c9e20617..11a4efa02 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -291,13 +291,9 @@ def transpose_character_before_cursor(cursor_offset, line): def transpose_word_before_cursor(cursor_offset, line): return cursor_offset, line # TODO Not implemented -# bonus functions (not part of readline) - - -@edit_keys.on('') -def delete_line(cursor_offset, line): - return 0, "" +# TODO undo all changes to line: meta-r +# bonus functions (not part of readline) @edit_keys.on('') def uppercase_next_word(cursor_offset, line): From 4ada2452459af87a47c6b49bbf447f3149f04aee Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 7 Mar 2015 09:21:10 -0500 Subject: [PATCH 0543/1650] fix multiline block completion errors on enter --- bpython/test/test_curtsies_painting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 7a378a9e9..157972108 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -168,6 +168,7 @@ def enter(self, line=None): autocompletion that would happen then, but intermediate stages won't happen""" if line is not None: + self.repl._set_cursor_offset(len(line), update_completion=False) self.repl.current_line = line with output_to_repl(self.repl): self.repl.on_enter(insert_into_history=False) From 25c2fe00657a5a0cc3505300ebb17d419270af7f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Mar 2015 21:30:12 +0100 Subject: [PATCH 0544/1650] Revert "Fix pkgutil.get_data for sample-config" This reverts commit 07d8d53f06863d051457f2f8845ad409e3b12828. --- bpython/curtsiesfrontend/repl.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 85c82f9d5..0226a3641 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -245,9 +245,6 @@ def load_module(self, name): self.watcher.track_module(module.__file__) return module - def get_data(self, pathname): - return open(pathname, "rb").read() - class ImportFinder(object): From 30f943f09d509157128a9d78e74893879cea8a3c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Mar 2015 22:08:10 +0100 Subject: [PATCH 0545/1650] Replace str(e) with "%s" % e where possible (fixes #501) Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 4 ++-- bpython/repl.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 0226a3641..c8fa404fe 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -562,7 +562,7 @@ def process_control_event(self, e): self.startup() except IOError as e: self.status_bar.message( - _('Executing PYTHONSTARTUP failed: %s') % (str(e))) + _('Executing PYTHONSTARTUP failed: %s') % (e, )) elif isinstance(e, bpythonevents.UndoEvent): self.undo(n=e.n) @@ -1532,7 +1532,7 @@ def show_source(self): try: source = self.get_source_of_current_name() except SourceNotFound as e: - self.status_bar.message(str(e)) + self.status_bar.message('%s' % (e, )) else: if self.config.highlight_show_source: source = format(PythonLexer().get_tokens(source), diff --git a/bpython/repl.py b/bpython/repl.py index e84aa3ae8..88e0c7f73 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -545,11 +545,11 @@ def get_source_of_current_name(self): obj = self.get_object(line) return inspection.get_source_unicode(obj) except (AttributeError, NameError) as e: - msg = _("Cannot get source: %s") % (str(e), ) + msg = _(u"Cannot get source: %s") % (e, ) except IOError as e: - msg = str(e) + msg = u"%s" % (e, ) except TypeError as e: - if "built-in" in str(e): + if "built-in" in u"%s" % (e, ): msg = _("Cannot access source of %r") % (obj, ) else: msg = _("No source code found for %s") % (self.current_line, ) From 834ffa0eece93d06dd11f9dfdfbb8c35b387c225 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Mar 2015 22:09:51 +0100 Subject: [PATCH 0546/1650] Do not mention search twice Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c8fa404fe..cb913b76f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1555,8 +1555,6 @@ def key_help_text(self): pairs = [] pairs.append(['complete history suggestion', 'right arrow at end of line']) pairs.append(['previous match with current line', 'up arrow']) - pairs.append(['reverse incremental search', 'M-r']) - pairs.append(['incremental search', 'M-s']) for functionality, key in [(attr[:-4].replace('_', ' '), getattr(self.config, attr)) for attr in self.config.__dict__ if attr.endswith('key')]: From 7eaef534e05de3d06a8c076bf5737050349bba89 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Mar 2015 22:19:52 +0100 Subject: [PATCH 0547/1650] Remove more calls to str Signed-off-by: Sebastian Ramacher --- bpython/config.py | 2 +- bpython/repl.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index fd613f46c..288bfb824 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -55,7 +55,7 @@ def fill_config_with_default_values(config, default_values): for (opt, val) in iteritems(default_values[section]): if not config.has_option(section, opt): - config.set(section, opt, str(val)) + config.set(section, opt, '%s' % (val, )) def loadini(struct, configfile): diff --git a/bpython/repl.py b/bpython/repl.py index 88e0c7f73..cb1a61fec 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -717,8 +717,7 @@ def write2file(self): with open(fn, mode) as f: f.write(s) except IOError as e: - self.interact.notify(_("Error writing file '%s': %s") % (fn, - str(e))) + self.interact.notify(_("Error writing file '%s': %s") % (fn, e)) else: self.interact.notify(_('Saved to %s.') % (fn, )) @@ -778,7 +777,7 @@ def do_pastebin_json(self, s): response = requests.post(url, data=payload, verify=True) response.raise_for_status() except requests.exceptions.RequestException as exc: - self.interact.notify(_('Upload failed: %s') % (str(exc), )) + self.interact.notify(_('Upload failed: %s') % (exc, )) return self.prev_pastebin_content = s @@ -866,7 +865,7 @@ def insert_into_history(self, s): self.rl_history.append_reload_and_write(s, self.config.hist_file, getpreferredencoding()) except RuntimeError as e: - self.interact.notify(str(e)) + self.interact.notify(u"%s" % (e, )) def prompt_undo(self): """Returns how many lines to undo, 0 means don't undo""" @@ -1081,7 +1080,7 @@ def edit_config(self): f.write(default_config) except (IOError, OSError) as e: self.interact.notify(_("Error writing file '%s': %s") % - (self.config.config.path, str(e))) + (self.config.config.path, e)) return False else: return False From e3dfb4f28c1ee10e4603cc976946fd29e055fa16 Mon Sep 17 00:00:00 2001 From: Amjith Ramanujam Date: Thu, 12 Mar 2015 22:59:33 -0700 Subject: [PATCH 0548/1650] Add Ctrl-e to complete current suggestion. --- bpython/curtsiesfrontend/repl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index cb913b76f..6f2055bf5 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -594,12 +594,11 @@ def process_key_event(self, e): if self.stdin.has_focus: return self.stdin.process_event(e) - if (e in ("", '') and + if (e in ("", '', '') and self.config.curtsies_right_arrow_completion and self.cursor_offset == len(self.current_line)): self.current_line += self.current_suggestion self.cursor_offset = len(self.current_line) - elif e in ("",) + key_dispatch[self.config.up_one_line_key]: self.up_one_line() elif e in ("",) + key_dispatch[self.config.down_one_line_key]: From 577b9c731d0ac559635fc2445a2ab3120b19d242 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 13 Mar 2015 13:01:52 -0400 Subject: [PATCH 0549/1650] use configurable keys for right arrow completion --- bpython/curtsiesfrontend/repl.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 6f2055bf5..f4da90230 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -594,9 +594,12 @@ def process_key_event(self, e): if self.stdin.has_focus: return self.stdin.process_event(e) - if (e in ("", '', '') and - self.config.curtsies_right_arrow_completion and - self.cursor_offset == len(self.current_line)): + if (e in (key_dispatch[self.config.right_key] + + key_dispatch[self.config.end_of_line_key] + + ("",)) + and self.config.curtsies_right_arrow_completion + and self.cursor_offset == len(self.current_line)): + self.current_line += self.current_suggestion self.cursor_offset = len(self.current_line) elif e in ("",) + key_dispatch[self.config.up_one_line_key]: From e83fb4a04960a8b165f5cf3915f8f9492edefa0d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 Mar 2015 18:39:54 +0100 Subject: [PATCH 0550/1650] Change default to None and set it to LOCK_EX in __init__ (fixes #509) Signed-off-by: Sebastian Ramacher --- bpython/filelock.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/filelock.py b/bpython/filelock.py index 94cb16d3e..a2d14cbe9 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -36,7 +36,10 @@ class FileLock(object): On platforms without fcntl, all operations in this class are no-ops. """ - def __init__(self, fd, mode=fcntl.LOCK_EX): + def __init__(self, fd, mode=None): + if has_fcntl and mode is None: + mode = fcntl.LOCK_EX + self.fd = fd self.mode = mode From 4d266217264e7872d43c5538c34bfb02d93f2880 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 Mar 2015 23:53:13 +0100 Subject: [PATCH 0551/1650] Move attr_{matches,lookup} to AttrCompleter Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 100 ++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 6e3ddb900..9c3a481d2 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -199,6 +199,9 @@ def format(self, filename): return filename +attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") + + class AttrCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): @@ -222,7 +225,7 @@ def matches(self, cursor_offset, line, **kwargs): break methodtext = text[-i:] matches = set(''.join([text[:-i], m]) - for m in attr_matches(methodtext, locals_)) + for m in self.attr_matches(methodtext, locals_)) # TODO add open paren for methods via _callable_prefix (or decide not # to) unless the first character is a _ filter out all attributes @@ -238,6 +241,51 @@ def locate(self, current_offset, line): def format(self, word): return after_last_dot(word) + def attr_matches(self, text, namespace): + """Taken from rlcompleter.py and bent to my will. + """ + + # Gna, Py 2.6's rlcompleter searches for __call__ inside the + # instance instead of the type, so we monkeypatch to prevent + # side-effects (__getattr__/__getattribute__) + m = attr_matches_re.match(text) + if not m: + return [] + + expr, attr = m.group(1, 3) + if expr.isdigit(): + # Special case: float literal, using attrs here will result in + # a SyntaxError + return [] + try: + obj = safe_eval(expr, namespace) + except EvaluationError: + return [] + with inspection.AttrCleaner(obj): + matches = self.attr_lookup(obj, expr, attr) + return matches + + def attr_lookup(self, obj, expr, attr): + """Second half of original attr_matches method factored out so it can + be wrapped in a safe try/finally block in case anything bad happens to + restore the original __getattribute__ method.""" + words = dir(obj) + if hasattr(obj, '__class__'): + words.append('__class__') + words = words + rlcompleter.get_class_members(obj.__class__) + if not isinstance(obj.__class__, abc.ABCMeta): + try: + words.remove('__abstractmethods__') + except ValueError: + pass + + matches = [] + n = len(attr) + for word in words: + if method_match(word, n, attr) and word != "__builtins__": + matches.append("%s.%s" % (expr, word)) + return matches + class DictKeyCompletion(BaseCompletionType): @@ -498,56 +546,6 @@ def safe_eval(expr, namespace): raise EvaluationError -attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") - - -def attr_matches(text, namespace): - """Taken from rlcompleter.py and bent to my will. - """ - - # Gna, Py 2.6's rlcompleter searches for __call__ inside the - # instance instead of the type, so we monkeypatch to prevent - # side-effects (__getattr__/__getattribute__) - m = attr_matches_re.match(text) - if not m: - return [] - - expr, attr = m.group(1, 3) - if expr.isdigit(): - # Special case: float literal, using attrs here will result in - # a SyntaxError - return [] - try: - obj = safe_eval(expr, namespace) - except EvaluationError: - return [] - with inspection.AttrCleaner(obj): - matches = attr_lookup(obj, expr, attr) - return matches - - -def attr_lookup(obj, expr, attr): - """Second half of original attr_matches method factored out so it can - be wrapped in a safe try/finally block in case anything bad happens to - restore the original __getattribute__ method.""" - words = dir(obj) - if hasattr(obj, '__class__'): - words.append('__class__') - words = words + rlcompleter.get_class_members(obj.__class__) - if not isinstance(obj.__class__, abc.ABCMeta): - try: - words.remove('__abstractmethods__') - except ValueError: - pass - - matches = [] - n = len(attr) - for word in words: - if method_match(word, n, attr) and word != "__builtins__": - matches.append("%s.%s" % (expr, word)) - return matches - - def _callable_postfix(value, word): """rlcompleter's _callable_postfix done right.""" with inspection.AttrCleaner(value): From 5cf6f520256ebada88de43ec7bc56e78b21e1c54 Mon Sep 17 00:00:00 2001 From: llllllllll Date: Sun, 15 Mar 2015 19:42:27 -0400 Subject: [PATCH 0552/1650] Makes old style classes and instances show __dict__ in the autocomplete. --- bpython/autocomplete.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 6e3ddb900..b2011674a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -33,6 +33,7 @@ import re import rlcompleter import sys +from types import InstanceType, ClassType from six.moves import range, builtins from six import string_types, iteritems @@ -539,6 +540,9 @@ def attr_lookup(obj, expr, attr): words.remove('__abstractmethods__') except ValueError: pass + if isinstance(obj, (InstanceType, ClassType)): + # Account for the __dict__ in an old-style class. + words.append('__dict__') matches = [] n = len(attr) From cb908722ac73c1aac0873039921e149276faba32 Mon Sep 17 00:00:00 2001 From: llllllllll Date: Sun, 15 Mar 2015 19:43:09 -0400 Subject: [PATCH 0553/1650] Adds tests to validate that __dict__ is in the autocomplete of an old style class or instance. --- bpython/test/test_autocomplete.py | 33 +++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index cad960e33..3b87ce720 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -215,13 +215,42 @@ def method(self, x): pass +class OldStyleFoo: + a = 10 + + def __init__(self): + self.b = 20 + + def method(self, x): + pass + + +skip_old_style = unittest.skipIf(py3, + 'In Python 3 there are no old style classes') + + class TestAttrCompletion(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.com = autocomplete.AttrCompletion() def test_att_matches_found_on_instance(self): - com = autocomplete.AttrCompletion() - self.assertSetEqual(com.matches(2, 'a.', locals_={'a': Foo()}), + self.assertSetEqual(self.com.matches(2, 'a.', locals_={'a': Foo()}), set(['a.method', 'a.a', 'a.b'])) + @skip_old_style + def test_att_matches_found_on_old_style_instance(self): + self.assertSetEqual(self.com.matches(2, 'a.', + locals_={'a': OldStyleFoo()}), + {'a.method', 'a.a', 'a.b'}) + self.assertIn(u'a.__dict__', + self.com.matches(3, 'a._', locals_={'a': OldStyleFoo()})) + + @skip_old_style + def test_att_matches_found_on_old_style_class_object(self): + self.assertIn(u'A.__dict__', + self.com.matches(3, 'A._', locals_={'A': OldStyleFoo})) + class TestMagicMethodCompletion(unittest.TestCase): From a82b4b5dba14a3a497560c583410f058a93a779f Mon Sep 17 00:00:00 2001 From: llllllllll Date: Sun, 15 Mar 2015 19:58:42 -0400 Subject: [PATCH 0554/1650] Makes the old-style class autocomplete work in 2.6 and 3.x --- bpython/autocomplete.py | 6 ++++-- bpython/test/test_autocomplete.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index b2011674a..3b64d0b2f 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -33,7 +33,6 @@ import re import rlcompleter import sys -from types import InstanceType, ClassType from six.moves import range, builtins from six import string_types, iteritems @@ -43,6 +42,9 @@ from bpython._py3compat import py3, try_decode from bpython.lazyre import LazyReCompile +if not py3: + from types import InstanceType, ClassType + # Autocomplete modes SIMPLE = 'simple' @@ -540,7 +542,7 @@ def attr_lookup(obj, expr, attr): words.remove('__abstractmethods__') except ValueError: pass - if isinstance(obj, (InstanceType, ClassType)): + if not py3 and isinstance(obj, (InstanceType, ClassType)): # Account for the __dict__ in an old-style class. words.append('__dict__') diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 3b87ce720..a6f2f153a 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -242,7 +242,7 @@ def test_att_matches_found_on_instance(self): def test_att_matches_found_on_old_style_instance(self): self.assertSetEqual(self.com.matches(2, 'a.', locals_={'a': OldStyleFoo()}), - {'a.method', 'a.a', 'a.b'}) + set(['a.method', 'a.a', 'a.b'])) self.assertIn(u'a.__dict__', self.com.matches(3, 'a._', locals_={'a': OldStyleFoo()})) From 2cfa83b1fb132b2f03e545fd896ff32474f4a5a0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Mar 2015 03:58:19 +0100 Subject: [PATCH 0555/1650] Re-introduce autocompletion modes Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 73 +++++++++++++++++++++++++++-------------- bpython/repl.py | 6 +++- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 9c3a481d2..b1d0969ed 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -68,11 +68,32 @@ def after_last_dot(name): return name.rstrip('.').rsplit('.')[-1] +def method_match_simple(word, size, text): + return word[:size] == text + + +def method_match_substring(word, size, text): + return text in word + + +def method_match_fuzzy(word, size, text): + s = r'.*%s.*' % '.*'.join(list(text)) + return re.search(s, word) + + +MODES_MAP = { + SIMPLE: method_match_simple, + SUBSTRING: method_match_substring, + FUZZY: method_match_fuzzy +} + + class BaseCompletionType(object): """Describes different completion types""" - def __init__(self, shown_before_tab=True): + def __init__(self, shown_before_tab=True, mode=SIMPLE): self._shown_before_tab = shown_before_tab + self.method_match = MODES_MAP[mode] def matches(self, cursor_offset, line, **kwargs): """Returns a list of possible matches given a line and cursor, or None @@ -112,17 +133,20 @@ def shown_before_tab(self): once that has happened.""" return self._shown_before_tab + def method_match(self, word, size, text): + return word[:size] == text + class CumulativeCompleter(BaseCompletionType): """Returns combined matches from several completers""" - def __init__(self, completers): + def __init__(self, completers, mode=SIMPLE): if not completers: raise ValueError( "CumulativeCompleter requires at least one completer") self._completers = completers - super(CumulativeCompleter, self).__init__(True) + super(CumulativeCompleter, self).__init__(True, mode) def locate(self, current_offset, line): return self._completers[0].locate(current_offset, line) @@ -158,8 +182,8 @@ def format(self, word): class FilenameCompletion(BaseCompletionType): - def __init__(self): - super(FilenameCompletion, self).__init__(False) + def __init__(self, mode=SIMPLE): + super(FilenameCompletion, self).__init__(False, mode) if sys.version_info[:2] >= (3, 4): def safe_glob(self, pathname): @@ -282,7 +306,7 @@ def attr_lookup(self, obj, expr, attr): matches = [] n = len(attr) for word in words: - if method_match(word, n, attr) and word != "__builtins__": + if self.method_match(word, n, attr) and word != "__builtins__": matches.append("%s.%s" % (expr, word)) return matches @@ -354,11 +378,11 @@ def matches(self, cursor_offset, line, **kwargs): matches = set() n = len(text) for word in KEYWORDS: - if method_match(word, n, text): + if self.method_match(word, n, text): matches.add(word) for nspace in (builtins.__dict__, locals_): for word, val in iteritems(nspace): - if method_match(word, n, text) and word != "__builtins__": + if self.method_match(word, n, text) and word != "__builtins__": word = try_decode(word, 'ascii') # if identifier isn't ascii, don't complete (syntax error) if word is None: @@ -508,28 +532,31 @@ def get_completer(completers, cursor_offset, line, **kwargs): """ for completer in completers: - matches = completer.matches( - cursor_offset, line, **kwargs) + matches = completer.matches(cursor_offset, line, **kwargs) if matches is not None: return sorted(matches), (completer if matches else None) return [], None -BPYTHON_COMPLETER = ( - DictKeyCompletion(), - StringLiteralAttrCompletion(), - ImportCompletion(), - FilenameCompletion(), - MagicMethodCompletion(), - MultilineJediCompletion(), - GlobalCompletion(), - CumulativeCompleter((AttrCompletion(), ParameterNameCompletion())) -) +def get_default_completer(mode=SIMPLE): + return ( + DictKeyCompletion(mode=mode), + StringLiteralAttrCompletion(mode=mode), + ImportCompletion(mode=mode), + FilenameCompletion(mode=mode), + MagicMethodCompletion(mode=mode), + MultilineJediCompletion(mode=mode), + GlobalCompletion(mode=mode), + CumulativeCompleter((AttrCompletion(mode=mode), + ParameterNameCompletion(mode=mode)), + mode=mode) + ) def get_completer_bpython(cursor_offset, line, **kwargs): """""" - return get_completer(BPYTHON_COMPLETER, cursor_offset, line, **kwargs) + return get_completer(get_default_completer(), + cursor_offset, line, **kwargs) class EvaluationError(Exception): @@ -552,7 +579,3 @@ def _callable_postfix(value, word): if inspection.is_callable(value): word += '(' return word - - -def method_match(word, size, text): - return word[:size] == text diff --git a/bpython/repl.py b/bpython/repl.py index cb1a61fec..3a55c4e2c 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -384,6 +384,9 @@ def __init__(self, interp, config): except EnvironmentError: pass + self.completers = autocomplete.get_default_completer( + config.autocomplete_mode) + @property def ps1(self): try: @@ -595,7 +598,8 @@ def complete(self, tab=False): self.set_docstring() - matches, completer = autocomplete.get_completer_bpython( + matches, completer = autocomplete.get_completer( + self.completers, cursor_offset=self.cursor_offset, line=self.current_line, locals_=self.interp.locals, From c830c7502c34b5e9f568df10c501ecd02f3d6fb4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Mar 2015 03:58:53 +0100 Subject: [PATCH 0556/1650] Update tests Signed-off-by: Sebastian Ramacher --- bpython/test/test_curtsies_repl.py | 6 +++--- bpython/test/test_repl.py | 20 ++++++++------------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 36b3546ef..fe2363914 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -179,7 +179,7 @@ def setUp(self): def test_list_win_visible_match_selected_on_tab_multiple_options(self): self.repl._current_line = " './'" self.repl._cursor_offset = 2 - with mock.patch('bpython.autocomplete.get_completer_bpython') as m: + with mock.patch('bpython.autocomplete.get_completer') as m: m.return_value = (['./abc', './abcd', './bcd'], autocomplete.FilenameCompletion()) self.repl.update_completion() @@ -191,7 +191,7 @@ def test_list_win_visible_match_selected_on_tab_multiple_options(self): def test_list_win_not_visible_and_cseq_if_cseq(self): self.repl._current_line = " './a'" self.repl._cursor_offset = 5 - with mock.patch('bpython.autocomplete.get_completer_bpython') as m: + with mock.patch('bpython.autocomplete.get_completer') as m: m.return_value = (['./abcd', './abce'], autocomplete.FilenameCompletion()) self.repl.update_completion() @@ -204,7 +204,7 @@ def test_list_win_not_visible_and_cseq_if_cseq(self): def test_list_win_not_visible_and_match_selected_if_one_option(self): self.repl._current_line = " './a'" self.repl._cursor_offset = 5 - with mock.patch('bpython.autocomplete.get_completer_bpython') as m: + with mock.patch('bpython.autocomplete.get_completer') as m: m.return_value = (['./abcd'], autocomplete.FilenameCompletion()) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index f4f8e6e7d..b2a55f1cc 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -316,24 +316,22 @@ def test_simple_global_complete(self): ['def', 'del', 'delattr(', 'dict(', 'dir(', 'divmod(']) - @unittest.skip("disabled while non-simple completion is disabled") def test_substring_global_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SUBSTRING}) self.set_input_line("time") self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.completer, 'matches')) - self.assertEqual(self.repl.completer.matches, + self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) + self.assertEqual(self.repl.matches_iter.matches, ['RuntimeError(', 'RuntimeWarning(']) - @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_global_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.FUZZY}) self.set_input_line("doc") self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.completer, 'matches')) - self.assertEqual(self.repl.completer.matches, + self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) + self.assertEqual(self.repl.matches_iter.matches, ['UnboundLocalError(', '__doc__']) # 2. Attribute tests @@ -349,7 +347,6 @@ def test_simple_attribute_complete(self): self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, ['Foo.bar']) - @unittest.skip("disabled while non-simple completion is disabled") def test_substring_attribute_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SUBSTRING}) self.set_input_line("Foo.az") @@ -359,10 +356,9 @@ def test_substring_attribute_complete(self): self.repl.push(line) self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.completer, 'matches')) - self.assertEqual(self.repl.completer.matches, ['Foo.baz']) + self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) + self.assertEqual(self.repl.matches_iter.matches, ['Foo.baz']) - @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_attribute_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.FUZZY}) self.set_input_line("Foo.br") @@ -372,8 +368,8 @@ def test_fuzzy_attribute_complete(self): self.repl.push(line) self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.completer, 'matches')) - self.assertEqual(self.repl.completer.matches, ['Foo.bar']) + self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) + self.assertEqual(self.repl.matches_iter.matches, ['Foo.bar']) # 3. Edge Cases def test_updating_namespace_complete(self): From 27b9493e634bf67d9586dbcad7ab9c636bb373bf Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 16 Mar 2015 04:28:15 +0100 Subject: [PATCH 0557/1650] Fix fuzzy completion test Signed-off-by: Sebastian Ramacher --- bpython/test/test_repl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index b2a55f1cc..7e0e7aaa8 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -332,7 +332,8 @@ def test_fuzzy_global_complete(self): self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, - ['UnboundLocalError(', '__doc__']) + ['UnboundLocalError(', '__doc__'] if not py3 else + ['ChildProcessError(', 'UnboundLocalError(', '__doc__']) # 2. Attribute tests def test_simple_attribute_complete(self): From ac630cc4dd630b7cb0e98eba8113b89271714cce Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 24 Mar 2015 00:19:14 +0100 Subject: [PATCH 0558/1650] Move pasting logic out of Repl Signed-off-by: Sebastian Ramacher --- bpython/paste.py | 112 +++++++++++++++++++++++++++++++++++++++++++++++ bpython/repl.py | 94 +++++++-------------------------------- 2 files changed, 127 insertions(+), 79 deletions(-) create mode 100644 bpython/paste.py diff --git a/bpython/paste.py b/bpython/paste.py new file mode 100644 index 000000000..237807287 --- /dev/null +++ b/bpython/paste.py @@ -0,0 +1,112 @@ +# encoding: utf-8 + +# The MIT License +# +# Copyright (c) 2014-2015 Sebastian Ramacher +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from locale import getpreferredencoding +from six.moves.urllib_parse import quote as urlquote, urljoin, urlparse +from string import Template +import errno +import requests +import subprocess +import unicodedata + +from bpython.translations import _ + + +class PasteFailed(Exception): + pass + + +class PastePinnwand(object): + def __init__(self, url, expiry, show_url, removal_url): + self.url = url + self.expiry = expiry + self.show_url = show_url + self.removal_url = removal_url + + def paste(self, s): + """Upload to pastebin via json interface.""" + + url = urljoin(self.url, '/json/new') + payload = { + 'code': s, + 'lexer': 'pycon', + 'expiry': self.expiry + } + + try: + response = requests.post(url, data=payload, verify=True) + response.raise_for_status() + except requests.exceptions.RequestException as exc: + raise PasteFailed(exc.message) + + data = response.json() + + paste_url_template = Template(self.show_url) + paste_id = urlquote(data['paste_id']) + paste_url = paste_url_template.safe_substitute(paste_id=paste_id) + + removal_url_template = Template(self.removal_url) + removal_id = urlquote(data['removal_id']) + removal_url = removal_url_template.safe_substitute( + removal_id=removal_id) + + return (paste_url, removal_url) + + +class PasteHelper(object): + def __init__(self, executable): + self.executable = executable + + def paste(self, s): + """Call out to helper program for pastebin upload.""" + + try: + helper = subprocess.Popen('', + executable=self.executable, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + helper.stdin.write(s.encode(getpreferredencoding())) + output = helper.communicate()[0].decode(getpreferredencoding()) + paste_url = output.split()[0] + except OSError as e: + if e.errno == errno.ENOENT: + raise PasteFailed(_('Helper program not found.')) + else: + raise PasteFailed(_('Helper program could not be run.')) + + if helper.returncode != 0: + raise PasteFailed(_('Helper program returned non-zero exit ' + 'status %d.' % (helper.returncode, ))) + + if not paste_url: + raise PasteFailed(_('No output from helper program.')) + else: + parsed_url = urlparse(paste_url) + if (not parsed_url.scheme + or any(unicodedata.category(c) == 'Cc' + for c in paste_url)): + raise PasteFailed(_('Failed to recognize the helper ' + 'program\'s output as an URL.')) + + return paste_url, diff --git a/bpython/repl.py b/bpython/repl.py index 3a55c4e2c..f46186238 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -24,13 +24,11 @@ # THE SOFTWARE. import code -import errno import inspect import io import os import pkgutil import pydoc -import requests import shlex import subprocess import sys @@ -38,12 +36,9 @@ import textwrap import time import traceback -import unicodedata from itertools import takewhile from locale import getpreferredencoding -from string import Template from six import itervalues -from six.moves.urllib_parse import quote as urlquote, urljoin, urlparse from pygments.token import Token @@ -53,6 +48,7 @@ from bpython.clipboard import get_clipboard, CopyFailed from bpython.formatter import Parenthesis from bpython.history import History +from bpython.paste import PasteHelper, PastePinnwand, PasteFailed from bpython.translations import _, ngettext @@ -386,6 +382,13 @@ def __init__(self, interp, config): self.completers = autocomplete.get_default_completer( config.autocomplete_mode) + if self.config.pastebin_helper: + self.paster = PasteHelper(self.config.pastebin_helper) + else: + self.paster = PastePinnwand(self.config.pastebin_url, + self.config.pastebin_expiry, + self.config.pastebin_show_url, + self.config.pastebin_removal_url) @property def ps1(self): @@ -761,91 +764,24 @@ def do_pastebin(self, s): self.prev_removal_url), 10) return self.prev_pastebin_url - if self.config.pastebin_helper: - return self.do_pastebin_helper(s) - else: - return self.do_pastebin_json(s) - - def do_pastebin_json(self, s): - """Upload to pastebin via json interface.""" - - url = urljoin(self.config.pastebin_url, '/json/new') - payload = { - 'code': s, - 'lexer': 'pycon', - 'expiry': self.config.pastebin_expiry - } self.interact.notify(_('Posting data to pastebin...')) try: - response = requests.post(url, data=payload, verify=True) - response.raise_for_status() - except requests.exceptions.RequestException as exc: - self.interact.notify(_('Upload failed: %s') % (exc, )) + paste_url, removal_url = self.paster.paste(s) + except PasteFailed as e: + self.interact.notify(_('Upload failed: %s') % e) return self.prev_pastebin_content = s - data = response.json() - - paste_url_template = Template(self.config.pastebin_show_url) - paste_id = urlquote(data['paste_id']) - paste_url = paste_url_template.safe_substitute(paste_id=paste_id) - - removal_url_template = Template(self.config.pastebin_removal_url) - removal_id = urlquote(data['removal_id']) - removal_url = removal_url_template.safe_substitute( - removal_id=removal_id) - self.prev_pastebin_url = paste_url self.prev_removal_url = removal_url - self.interact.notify(_('Pastebin URL: %s - Removal URL: %s') % - (paste_url, removal_url), 10) - - return paste_url - def do_pastebin_helper(self, s): - """Call out to helper program for pastebin upload.""" - self.interact.notify(_('Posting data to pastebin...')) - - try: - helper = subprocess.Popen('', - executable=self.config.pastebin_helper, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - helper.stdin.write(s.encode(getpreferredencoding())) - output = helper.communicate()[0].decode(getpreferredencoding()) - paste_url = output.split()[0] - except OSError as e: - if e.errno == errno.ENOENT: - self.interact.notify(_('Upload failed: ' - 'Helper program not found.')) - else: - self.interact.notify(_('Upload failed: ' - 'Helper program could not be run.')) - return - - if helper.returncode != 0: - self.interact.notify(_('Upload failed: ' - 'Helper program returned non-zero exit ' - 'status %d.' % (helper.returncode, ))) - return - - if not paste_url: - self.interact.notify(_('Upload failed: ' - 'No output from helper program.')) - return + if removal_url is not None: + self.interact.notify(_('Pastebin URL: %s - Removal URL: %s') % + (paste_url, removal_url), 10) else: - parsed_url = urlparse(paste_url) - if (not parsed_url.scheme - or any(unicodedata.category(c) == 'Cc' - for c in paste_url)): - self.interact.notify(_("Upload failed: " - "Failed to recognize the helper " - "program's output as an URL.")) - return + self.interact.notify(_('Pastebin URL: %s') % (paste_url, ), 10) - self.prev_pastebin_content = s - self.interact.notify(_('Pastebin URL: %s') % (paste_url, ), 10) return paste_url def push(self, s, insert_into_history=True): From 5cdfa51abef618086014417de24df116acfb9eef Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 24 Mar 2015 00:31:12 +0100 Subject: [PATCH 0559/1650] Update translations Signed-off-by: Sebastian Ramacher --- bpython/translations/bpython.pot | 122 +++++++------- .../translations/de/LC_MESSAGES/bpython.po | 142 ++++++++-------- .../translations/es_ES/LC_MESSAGES/bpython.po | 120 ++++++------- .../translations/fr_FR/LC_MESSAGES/bpython.po | 157 +++++++++--------- .../translations/it_IT/LC_MESSAGES/bpython.po | 120 ++++++------- .../translations/nl_NL/LC_MESSAGES/bpython.po | 120 ++++++------- 6 files changed, 389 insertions(+), 392 deletions(-) diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 00b44b691..074e4a475 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.13-641\n" +"Project-Id-Version: bpython 0.15.dev98\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-02-16 21:56+0100\n" +"POT-Creation-Date: 2015-03-24 00:25+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -68,159 +68,159 @@ msgstr "" msgid "Show Source" msgstr "" -#: bpython/curtsies.py:36 +#: bpython/curtsies.py:37 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:38 +#: bpython/curtsies.py:39 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:223 +#: bpython/history.py:228 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:537 +#: bpython/paste.py:94 +msgid "Helper program not found." +msgstr "" + +#: bpython/paste.py:96 +msgid "Helper program could not be run." +msgstr "" + +#: bpython/paste.py:100 +#, python-format +msgid "Helper program returned non-zero exit status %d." +msgstr "" + +#: bpython/paste.py:103 +msgid "No output from helper program." +msgstr "" + +#: bpython/paste.py:109 +msgid "Failed to recognize the helper program's output as an URL." +msgstr "" + +#: bpython/repl.py:549 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:542 +#: bpython/repl.py:554 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:547 +#: bpython/repl.py:559 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:549 +#: bpython/repl.py:561 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:681 +#: bpython/repl.py:694 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 +#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:696 +#: bpython/repl.py:709 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:700 +#: bpython/repl.py:713 msgid "overwrite" msgstr "" -#: bpython/repl.py:702 +#: bpython/repl.py:715 msgid "append" msgstr "" -#: bpython/repl.py:714 bpython/repl.py:1075 +#: bpython/repl.py:727 bpython/repl.py:1022 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:717 +#: bpython/repl.py:729 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:723 +#: bpython/repl.py:735 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:730 +#: bpython/repl.py:742 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:732 +#: bpython/repl.py:744 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:741 +#: bpython/repl.py:753 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:754 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:749 +#: bpython/repl.py:761 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:770 bpython/repl.py:799 +#: bpython/repl.py:768 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:775 +#: bpython/repl.py:772 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:792 +#: bpython/repl.py:780 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:811 -msgid "Upload failed: Helper program not found." -msgstr "" - -#: bpython/repl.py:814 -msgid "Upload failed: Helper program could not be run." -msgstr "" - -#: bpython/repl.py:821 -#, python-format -msgid "Upload failed: Helper program returned non-zero exit status %d." -msgstr "" - -#: bpython/repl.py:825 -msgid "Upload failed: No output from helper program." -msgstr "" - -#: bpython/repl.py:833 -msgid "Upload failed: Failed to recognize the helper program's output as an URL." -msgstr "" - -#: bpython/repl.py:839 +#: bpython/repl.py:783 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:872 +#: bpython/repl.py:817 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:879 bpython/repl.py:883 +#: bpython/repl.py:824 bpython/repl.py:828 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:831 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1062 +#: bpython/repl.py:1007 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1082 +#: bpython/repl.py:1029 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1085 +#: bpython/repl.py:1032 msgid "Error editing config file." msgstr "" @@ -251,39 +251,39 @@ msgstr "" msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:286 +#: bpython/curtsiesfrontend/repl.py:344 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:287 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:526 +#: bpython/curtsiesfrontend/repl.py:565 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:543 +#: bpython/curtsiesfrontend/repl.py:582 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:814 +#: bpython/curtsiesfrontend/repl.py:855 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:820 +#: bpython/curtsiesfrontend/repl.py:861 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:825 +#: bpython/curtsiesfrontend/repl.py:866 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:830 +#: bpython/curtsiesfrontend/repl.py:871 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 5afe91d62..094fab1e5 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,21 +7,23 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-16 21:56+0100\n" -"PO-Revision-Date: 2015-02-05 14:54+0100\n" +"POT-Creation-Date: 2015-03-24 00:25+0100\n" +"PO-Revision-Date: 2015-03-24 00:27+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: de \n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" +"Language: de\n" +"X-Generator: Poedit 1.6.10\n" #: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back " -"to the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back to " +"the regular Python interpreter." msgstr "" #: bpython/args.py:69 @@ -68,167 +70,164 @@ msgstr "" msgid "Show Source" msgstr "Quellcode anzeigen" -#: bpython/curtsies.py:36 +#: bpython/curtsies.py:37 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:38 +#: bpython/curtsies.py:39 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:223 +#: bpython/history.py:228 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" -#: bpython/repl.py:537 +#: bpython/paste.py:94 +msgid "Helper program not found." +msgstr "Hilfsprogramm konnte nicht gefunden werden." + +#: bpython/paste.py:96 +msgid "Helper program could not be run." +msgstr "Hilfsprogramm konnte nicht ausgeführt werden." + +#: bpython/paste.py:100 +#, python-format +msgid "Helper program returned non-zero exit status %d." +msgstr "Hilfsprogramm beendete mit Status %d." + +#: bpython/paste.py:103 +msgid "No output from helper program." +msgstr "Keine Ausgabe von Hilfsprogramm vorhanden." + +#: bpython/paste.py:109 +msgid "Failed to recognize the helper program's output as an URL." +msgstr "Konnte Ausgabe von Hilfsprogramm nicht verarbeiten." + +#: bpython/repl.py:549 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:542 +#: bpython/repl.py:554 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" -#: bpython/repl.py:547 +#: bpython/repl.py:559 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/repl.py:549 +#: bpython/repl.py:561 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" -#: bpython/repl.py:681 +#: bpython/repl.py:694 msgid "Save to file (Esc to cancel): " msgstr "In Datei speichern (Esc um abzubrechen): " -#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 +#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 msgid "Save cancelled." msgstr "Speichern abgebrochen." -#: bpython/repl.py:696 +#: bpython/repl.py:709 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " -msgstr "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" +msgstr "" +"%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" -#: bpython/repl.py:700 +#: bpython/repl.py:713 msgid "overwrite" msgstr "überschreiben" -#: bpython/repl.py:702 +#: bpython/repl.py:715 msgid "append" msgstr "anhängen" -#: bpython/repl.py:714 bpython/repl.py:1075 +#: bpython/repl.py:727 bpython/repl.py:1022 #, python-format msgid "Error writing file '%s': %s" msgstr "Fehler beim Schreiben in Datei '%s': %s" -#: bpython/repl.py:717 +#: bpython/repl.py:729 #, python-format msgid "Saved to %s." msgstr "Nach %s gespeichert." -#: bpython/repl.py:723 +#: bpython/repl.py:735 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/repl.py:730 +#: bpython/repl.py:742 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/repl.py:732 +#: bpython/repl.py:744 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:741 +#: bpython/repl.py:753 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:754 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:749 +#: bpython/repl.py:761 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:770 bpython/repl.py:799 +#: bpython/repl.py:768 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." -#: bpython/repl.py:775 +#: bpython/repl.py:772 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:792 +#: bpython/repl.py:780 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:811 -msgid "Upload failed: Helper program not found." -msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht gefunden werden." - -#: bpython/repl.py:814 -msgid "Upload failed: Helper program could not be run." -msgstr "" -"Hochladen ist fehlgeschlagen: Hilfsprogramm konnte nicht ausgeführt " -"werden." - -#: bpython/repl.py:821 -#, python-format -msgid "Upload failed: Helper program returned non-zero exit status %d." -msgstr "Hochladen ist fehlgeschlagen: Hilfsprogramm beendete mit Status %d." - -#: bpython/repl.py:825 -msgid "Upload failed: No output from helper program." -msgstr "Hochladen ist fehlgeschlagen: Keine Ausgabe von Hilfsprogramm vorhanden." - -#: bpython/repl.py:833 -msgid "Upload failed: Failed to recognize the helper program's output as an URL." -msgstr "" -"Hochladen ist fehlgeschlagen: Konte Ausgabe von Hilfsprogramm nicht " -"verarbeiten." - -#: bpython/repl.py:839 +#: bpython/repl.py:783 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:872 +#: bpython/repl.py:817 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:879 bpython/repl.py:883 +#: bpython/repl.py:824 bpython/repl.py:828 msgid "Undo canceled" msgstr "Rückgängigmachen abgebrochen" -#: bpython/repl.py:886 +#: bpython/repl.py:831 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1062 +#: bpython/repl.py:1007 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " -"werden? (j/N)" +"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt werden? " +"(j/N)" -#: bpython/repl.py:1082 +#: bpython/repl.py:1029 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" "bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " "Änderungen übernommen werden." -#: bpython/repl.py:1085 +#: bpython/repl.py:1032 msgid "Error editing config file." msgstr "Fehler beim Bearbeiten der Konfigurationsdatei." @@ -259,39 +258,38 @@ msgstr "" msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:286 +#: bpython/curtsiesfrontend/repl.py:344 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" -#: bpython/curtsiesfrontend/repl.py:287 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#: bpython/curtsiesfrontend/repl.py:526 +#: bpython/curtsiesfrontend/repl.py:565 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" -#: bpython/curtsiesfrontend/repl.py:543 +#: bpython/curtsiesfrontend/repl.py:582 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:814 +#: bpython/curtsiesfrontend/repl.py:855 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:820 +#: bpython/curtsiesfrontend/repl.py:861 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:825 +#: bpython/curtsiesfrontend/repl.py:866 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:830 +#: bpython/curtsiesfrontend/repl.py:871 msgid "Auto-reloading not available because watchdog not installed." msgstr "" - diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 34604d59f..d88ebcc9d 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-16 21:56+0100\n" +"POT-Creation-Date: 2015-03-24 00:25+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" @@ -68,159 +68,159 @@ msgstr "" msgid "Show Source" msgstr "" -#: bpython/curtsies.py:36 +#: bpython/curtsies.py:37 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:38 +#: bpython/curtsies.py:39 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:223 +#: bpython/history.py:228 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:537 +#: bpython/paste.py:94 +msgid "Helper program not found." +msgstr "" + +#: bpython/paste.py:96 +msgid "Helper program could not be run." +msgstr "" + +#: bpython/paste.py:100 +#, python-format +msgid "Helper program returned non-zero exit status %d." +msgstr "" + +#: bpython/paste.py:103 +msgid "No output from helper program." +msgstr "" + +#: bpython/paste.py:109 +msgid "Failed to recognize the helper program's output as an URL." +msgstr "" + +#: bpython/repl.py:549 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:542 +#: bpython/repl.py:554 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:547 +#: bpython/repl.py:559 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:549 +#: bpython/repl.py:561 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:681 +#: bpython/repl.py:694 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 +#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:696 +#: bpython/repl.py:709 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:700 +#: bpython/repl.py:713 msgid "overwrite" msgstr "" -#: bpython/repl.py:702 +#: bpython/repl.py:715 msgid "append" msgstr "" -#: bpython/repl.py:714 bpython/repl.py:1075 +#: bpython/repl.py:727 bpython/repl.py:1022 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:717 +#: bpython/repl.py:729 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:723 +#: bpython/repl.py:735 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:730 +#: bpython/repl.py:742 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:732 +#: bpython/repl.py:744 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:741 +#: bpython/repl.py:753 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:754 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:749 +#: bpython/repl.py:761 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:770 bpython/repl.py:799 +#: bpython/repl.py:768 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:775 +#: bpython/repl.py:772 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:792 +#: bpython/repl.py:780 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:811 -msgid "Upload failed: Helper program not found." -msgstr "" - -#: bpython/repl.py:814 -msgid "Upload failed: Helper program could not be run." -msgstr "" - -#: bpython/repl.py:821 -#, python-format -msgid "Upload failed: Helper program returned non-zero exit status %d." -msgstr "" - -#: bpython/repl.py:825 -msgid "Upload failed: No output from helper program." -msgstr "" - -#: bpython/repl.py:833 -msgid "Upload failed: Failed to recognize the helper program's output as an URL." -msgstr "" - -#: bpython/repl.py:839 +#: bpython/repl.py:783 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:872 +#: bpython/repl.py:817 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:879 bpython/repl.py:883 +#: bpython/repl.py:824 bpython/repl.py:828 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:831 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1062 +#: bpython/repl.py:1007 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1082 +#: bpython/repl.py:1029 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1085 +#: bpython/repl.py:1032 msgid "Error editing config file." msgstr "" @@ -253,39 +253,39 @@ msgstr "" msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:286 +#: bpython/curtsiesfrontend/repl.py:344 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:287 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:526 +#: bpython/curtsiesfrontend/repl.py:565 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:543 +#: bpython/curtsiesfrontend/repl.py:582 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:814 +#: bpython/curtsiesfrontend/repl.py:855 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:820 +#: bpython/curtsiesfrontend/repl.py:861 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:825 +#: bpython/curtsiesfrontend/repl.py:866 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:830 +#: bpython/curtsiesfrontend/repl.py:871 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index 1bb4aaff5..cc326c2de 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,25 +6,27 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-16 21:56+0100\n" -"PO-Revision-Date: 2015-02-05 14:54+0100\n" +"POT-Creation-Date: 2015-03-24 00:25+0100\n" +"PO-Revision-Date: 2015-03-24 00:29+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" -"Plural-Forms: nplurals=2; plural=(n > 1)\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" +"Language: fr_FR\n" +"X-Generator: Poedit 1.6.10\n" #: bpython/args.py:59 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back " -"to the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back to " +"the regular Python interpreter." msgstr "" "Utilisation: %prog [options] [fichier [arguments]]\n" -"NOTE: Si bpython ne reconnaît pas un des arguments fournis, " -"l'interpréteur Python classique sera lancé" +"NOTE: Si bpython ne reconnaît pas un des arguments fournis, l'interpréteur " +"Python classique sera lancé" #: bpython/args.py:69 msgid "Use CONFIG instead of default config file." @@ -33,8 +35,7 @@ msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." #: bpython/args.py:71 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -"Aller dans le shell bpython après l'exécution du fichier au lieu de " -"quitter." +"Aller dans le shell bpython après l'exécution du fichier au lieu de quitter." #: bpython/args.py:74 msgid "Don't flush the output to stdout." @@ -72,163 +73,161 @@ msgstr "" msgid "Show Source" msgstr "Montrer le code source" -#: bpython/curtsies.py:36 +#: bpython/curtsies.py:37 msgid "log debug messages to bpython.log" msgstr "logger les messages de debug dans bpython.log" -#: bpython/curtsies.py:38 +#: bpython/curtsies.py:39 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:223 +#: bpython/history.py:228 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" -#: bpython/repl.py:537 +#: bpython/paste.py:94 +msgid "Helper program not found." +msgstr "programme externe non trouvé." + +#: bpython/paste.py:96 +msgid "Helper program could not be run." +msgstr "impossible de lancer le programme externe." + +#: bpython/paste.py:100 +#, python-format +msgid "Helper program returned non-zero exit status %d." +msgstr "" +"le programme externe a renvoyé un statut de sortie différent de zéro %d." + +#: bpython/paste.py:103 +msgid "No output from helper program." +msgstr "pas de sortie du programme externe." + +#: bpython/paste.py:109 +msgid "Failed to recognize the helper program's output as an URL." +msgstr "la sortie du programme externe ne correspond pas à une URL." + +#: bpython/repl.py:549 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:542 +#: bpython/repl.py:554 #, python-format msgid "Cannot get source: %s" msgstr "Impossible de récupérer le source: %s" -#: bpython/repl.py:547 +#: bpython/repl.py:559 #, python-format msgid "Cannot access source of %r" msgstr "Impossible d'accéder au source de %r" -#: bpython/repl.py:549 +#: bpython/repl.py:561 #, python-format msgid "No source code found for %s" msgstr "Pas de code source trouvé pour %s" -#: bpython/repl.py:681 +#: bpython/repl.py:694 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 +#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:696 +#: bpython/repl.py:709 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:700 +#: bpython/repl.py:713 msgid "overwrite" msgstr "" -#: bpython/repl.py:702 +#: bpython/repl.py:715 msgid "append" msgstr "" -#: bpython/repl.py:714 bpython/repl.py:1075 +#: bpython/repl.py:727 bpython/repl.py:1022 #, python-format msgid "Error writing file '%s': %s" msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" -#: bpython/repl.py:717 +#: bpython/repl.py:729 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:723 +#: bpython/repl.py:735 msgid "No clipboard available." msgstr "Pas de presse-papier disponible." -#: bpython/repl.py:730 +#: bpython/repl.py:742 msgid "Could not copy to clipboard." msgstr "Impossible de copier vers le presse-papier." -#: bpython/repl.py:732 +#: bpython/repl.py:744 msgid "Copied content to clipboard." msgstr "Contenu copié vers le presse-papier." -#: bpython/repl.py:741 +#: bpython/repl.py:753 msgid "Pastebin buffer? (y/N) " msgstr "Tampon Pastebin ? (o/N) " -#: bpython/repl.py:742 +#: bpython/repl.py:754 msgid "Pastebin aborted." msgstr "Pastebin abandonné." -#: bpython/repl.py:749 +#: bpython/repl.py:761 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" -#: bpython/repl.py:770 bpython/repl.py:799 +#: bpython/repl.py:768 msgid "Posting data to pastebin..." msgstr "Envoi des donnés à pastebin..." -#: bpython/repl.py:775 +#: bpython/repl.py:772 #, python-format msgid "Upload failed: %s" msgstr "Echec du téléchargement: %s" -#: bpython/repl.py:792 +#: bpython/repl.py:780 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "URL Pastebin: %s - URL de suppression: %s" -#: bpython/repl.py:811 -msgid "Upload failed: Helper program not found." -msgstr "Echec de l'upload: programme externe non trouvé." - -#: bpython/repl.py:814 -msgid "Upload failed: Helper program could not be run." -msgstr "Echec de l'upload: impossible de lancer le programme externe." - -#: bpython/repl.py:821 -#, python-format -msgid "Upload failed: Helper program returned non-zero exit status %d." -msgstr "" -"Echec de l'upload: le programme externe a renvoyé un statut de sortie " -"différent de zéro %d." - -#: bpython/repl.py:825 -msgid "Upload failed: No output from helper program." -msgstr "Echec de l'upload: pas de sortie du programme externe." - -#: bpython/repl.py:833 -msgid "Upload failed: Failed to recognize the helper program's output as an URL." -msgstr "" -"Echec de l'upload: la sortie du programme externe ne correspond pas à une" -" URL." - -#: bpython/repl.py:839 +#: bpython/repl.py:783 #, python-format msgid "Pastebin URL: %s" msgstr "URL Pastebin: %s" -#: bpython/repl.py:872 +#: bpython/repl.py:817 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:879 bpython/repl.py:883 +#: bpython/repl.py:824 bpython/repl.py:828 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:831 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1062 +#: bpython/repl.py:1007 msgid "Config file does not exist - create new from default? (y/N)" -msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" +msgstr "" +"Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" -#: bpython/repl.py:1082 +#: bpython/repl.py:1029 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1085 +#: bpython/repl.py:1032 msgid "Error editing config file." msgstr "" @@ -236,8 +235,8 @@ msgstr "" #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -" <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> " -"Montrer Source " +" <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> Montrer " +"Source " #: bpython/urwid.py:1128 msgid "Run twisted reactor." @@ -245,7 +244,8 @@ msgstr "Lancer le reactor twisted." #: bpython/urwid.py:1130 msgid "Select specific reactor (see --help-reactors). Implies --twisted." -msgstr "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." +msgstr "" +"Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." #: bpython/urwid.py:1133 msgid "List available reactors for -r." @@ -256,46 +256,45 @@ msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" " -"pour donner plus d'options au plugin." +"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" pour " +"donner plus d'options au plugin." #: bpython/urwid.py:1138 msgid "Port to run an eval server on (forces Twisted)." msgstr "Port pour lancer un server eval (force Twisted)." -#: bpython/curtsiesfrontend/repl.py:286 +#: bpython/curtsiesfrontend/repl.py:344 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" -#: bpython/curtsiesfrontend/repl.py:287 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." -#: bpython/curtsiesfrontend/repl.py:526 +#: bpython/curtsiesfrontend/repl.py:565 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" -#: bpython/curtsiesfrontend/repl.py:543 +#: bpython/curtsiesfrontend/repl.py:582 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:814 +#: bpython/curtsiesfrontend/repl.py:855 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:820 +#: bpython/curtsiesfrontend/repl.py:861 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:825 +#: bpython/curtsiesfrontend/repl.py:866 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:830 +#: bpython/curtsiesfrontend/repl.py:871 msgid "Auto-reloading not available because watchdog not installed." msgstr "" - diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index 08446d970..66cd0d5a5 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-16 21:56+0100\n" +"POT-Creation-Date: 2015-03-24 00:25+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: Michele Orrù\n" @@ -68,159 +68,159 @@ msgstr "" msgid "Show Source" msgstr "" -#: bpython/curtsies.py:36 +#: bpython/curtsies.py:37 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:38 +#: bpython/curtsies.py:39 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:223 +#: bpython/history.py:228 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:537 +#: bpython/paste.py:94 +msgid "Helper program not found." +msgstr "" + +#: bpython/paste.py:96 +msgid "Helper program could not be run." +msgstr "" + +#: bpython/paste.py:100 +#, python-format +msgid "Helper program returned non-zero exit status %d." +msgstr "" + +#: bpython/paste.py:103 +msgid "No output from helper program." +msgstr "" + +#: bpython/paste.py:109 +msgid "Failed to recognize the helper program's output as an URL." +msgstr "" + +#: bpython/repl.py:549 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:542 +#: bpython/repl.py:554 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:547 +#: bpython/repl.py:559 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:549 +#: bpython/repl.py:561 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:681 +#: bpython/repl.py:694 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 +#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:696 +#: bpython/repl.py:709 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:700 +#: bpython/repl.py:713 msgid "overwrite" msgstr "" -#: bpython/repl.py:702 +#: bpython/repl.py:715 msgid "append" msgstr "" -#: bpython/repl.py:714 bpython/repl.py:1075 +#: bpython/repl.py:727 bpython/repl.py:1022 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:717 +#: bpython/repl.py:729 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:723 +#: bpython/repl.py:735 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:730 +#: bpython/repl.py:742 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:732 +#: bpython/repl.py:744 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:741 +#: bpython/repl.py:753 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:754 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:749 +#: bpython/repl.py:761 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:770 bpython/repl.py:799 +#: bpython/repl.py:768 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:775 +#: bpython/repl.py:772 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:792 +#: bpython/repl.py:780 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:811 -msgid "Upload failed: Helper program not found." -msgstr "" - -#: bpython/repl.py:814 -msgid "Upload failed: Helper program could not be run." -msgstr "" - -#: bpython/repl.py:821 -#, python-format -msgid "Upload failed: Helper program returned non-zero exit status %d." -msgstr "" - -#: bpython/repl.py:825 -msgid "Upload failed: No output from helper program." -msgstr "" - -#: bpython/repl.py:833 -msgid "Upload failed: Failed to recognize the helper program's output as an URL." -msgstr "" - -#: bpython/repl.py:839 +#: bpython/repl.py:783 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:872 +#: bpython/repl.py:817 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:879 bpython/repl.py:883 +#: bpython/repl.py:824 bpython/repl.py:828 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:831 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1062 +#: bpython/repl.py:1007 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1082 +#: bpython/repl.py:1029 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1085 +#: bpython/repl.py:1032 msgid "Error editing config file." msgstr "" @@ -251,39 +251,39 @@ msgstr "" msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:286 +#: bpython/curtsiesfrontend/repl.py:344 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:287 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:526 +#: bpython/curtsiesfrontend/repl.py:565 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:543 +#: bpython/curtsiesfrontend/repl.py:582 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:814 +#: bpython/curtsiesfrontend/repl.py:855 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:820 +#: bpython/curtsiesfrontend/repl.py:861 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:825 +#: bpython/curtsiesfrontend/repl.py:866 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:830 +#: bpython/curtsiesfrontend/repl.py:871 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index 2da674b5e..c82908ee7 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-02-16 21:56+0100\n" +"POT-Creation-Date: 2015-03-24 00:25+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language-Team: bpython developers\n" @@ -68,159 +68,159 @@ msgstr "" msgid "Show Source" msgstr "" -#: bpython/curtsies.py:36 +#: bpython/curtsies.py:37 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:38 +#: bpython/curtsies.py:39 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:223 +#: bpython/history.py:228 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/repl.py:537 +#: bpython/paste.py:94 +msgid "Helper program not found." +msgstr "" + +#: bpython/paste.py:96 +msgid "Helper program could not be run." +msgstr "" + +#: bpython/paste.py:100 +#, python-format +msgid "Helper program returned non-zero exit status %d." +msgstr "" + +#: bpython/paste.py:103 +msgid "No output from helper program." +msgstr "" + +#: bpython/paste.py:109 +msgid "Failed to recognize the helper program's output as an URL." +msgstr "" + +#: bpython/repl.py:549 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:542 +#: bpython/repl.py:554 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:547 +#: bpython/repl.py:559 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:549 +#: bpython/repl.py:561 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:681 +#: bpython/repl.py:694 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:683 bpython/repl.py:686 bpython/repl.py:705 +#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:696 +#: bpython/repl.py:709 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:700 +#: bpython/repl.py:713 msgid "overwrite" msgstr "" -#: bpython/repl.py:702 +#: bpython/repl.py:715 msgid "append" msgstr "" -#: bpython/repl.py:714 bpython/repl.py:1075 +#: bpython/repl.py:727 bpython/repl.py:1022 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:717 +#: bpython/repl.py:729 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:723 +#: bpython/repl.py:735 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:730 +#: bpython/repl.py:742 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:732 +#: bpython/repl.py:744 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:741 +#: bpython/repl.py:753 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:754 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:749 +#: bpython/repl.py:761 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:770 bpython/repl.py:799 +#: bpython/repl.py:768 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:775 +#: bpython/repl.py:772 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:792 +#: bpython/repl.py:780 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:811 -msgid "Upload failed: Helper program not found." -msgstr "" - -#: bpython/repl.py:814 -msgid "Upload failed: Helper program could not be run." -msgstr "" - -#: bpython/repl.py:821 -#, python-format -msgid "Upload failed: Helper program returned non-zero exit status %d." -msgstr "" - -#: bpython/repl.py:825 -msgid "Upload failed: No output from helper program." -msgstr "" - -#: bpython/repl.py:833 -msgid "Upload failed: Failed to recognize the helper program's output as an URL." -msgstr "" - -#: bpython/repl.py:839 +#: bpython/repl.py:783 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:872 +#: bpython/repl.py:817 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:879 bpython/repl.py:883 +#: bpython/repl.py:824 bpython/repl.py:828 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:831 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1062 +#: bpython/repl.py:1007 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1082 +#: bpython/repl.py:1029 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1085 +#: bpython/repl.py:1032 msgid "Error editing config file." msgstr "" @@ -251,39 +251,39 @@ msgstr "" msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:286 +#: bpython/curtsiesfrontend/repl.py:344 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:287 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:526 +#: bpython/curtsiesfrontend/repl.py:565 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:543 +#: bpython/curtsiesfrontend/repl.py:582 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:814 +#: bpython/curtsiesfrontend/repl.py:855 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:820 +#: bpython/curtsiesfrontend/repl.py:861 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:825 +#: bpython/curtsiesfrontend/repl.py:866 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:830 +#: bpython/curtsiesfrontend/repl.py:871 msgid "Auto-reloading not available because watchdog not installed." msgstr "" From 21b9a5416410b8b7d680e2557741b8802a990e11 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 24 Mar 2015 22:15:55 +0100 Subject: [PATCH 0560/1650] Use bpython.config's getpreferredencoding Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index f46186238..349cdc00b 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -37,7 +37,6 @@ import time import traceback from itertools import takewhile -from locale import getpreferredencoding from six import itervalues from pygments.token import Token @@ -46,6 +45,7 @@ from bpython import inspection from bpython._py3compat import PythonLexer, py3, prepare_for_exec from bpython.clipboard import get_clipboard, CopyFailed +from bpython.config import getpreferredencoding from bpython.formatter import Parenthesis from bpython.history import History from bpython.paste import PasteHelper, PastePinnwand, PasteFailed From 38f1ed6bc30283ce036bc069ca9b3d8d8de28278 Mon Sep 17 00:00:00 2001 From: Michael Mulley Date: Wed, 25 Mar 2015 16:13:52 -0400 Subject: [PATCH 0561/1650] Remember the source of interactively defined functions - Patches the linecache module to achieve this - inspect.getsource() works for functions defined on the console - tracebacks show code for frames inside interactively defined functions fixes bpython/bpython#229 --- bpython/history.py | 72 ++++++++++++++++++++++++++++++++ bpython/repl.py | 6 ++- bpython/test/test_interpreter.py | 26 +++++++++--- 3 files changed, 97 insertions(+), 7 deletions(-) diff --git a/bpython/history.py b/bpython/history.py index e08ec2684..a6f632f08 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -25,6 +25,7 @@ from __future__ import unicode_literals import io +import linecache import os import stat from itertools import islice @@ -232,3 +233,74 @@ def append_reload_and_write(self, s, filename, encoding): # Make sure that entries contains at least one element. If the # file and s are empty, this can occur. self.entries = [''] + +class BPythonLinecache(dict): + """Replaces the cache dict in the standard-library linecache module, + to also remember (in an unerasable way) bpython console input.""" + + def __init__(self, *args, **kwargs): + dict.__init__(self, *args, **kwargs) + self.bpython_history = [] + + def is_bpython_filename(self, fname): + try: + return fname.startswith('' diff --git a/bpython/repl.py b/bpython/repl.py index 349cdc00b..d71aba2c3 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -47,7 +47,7 @@ from bpython.clipboard import get_clipboard, CopyFailed from bpython.config import getpreferredencoding from bpython.formatter import Parenthesis -from bpython.history import History +from bpython.history import History, filename_for_console_input from bpython.paste import PasteHelper, PastePinnwand, PasteFailed from bpython.translations import _, ngettext @@ -94,7 +94,7 @@ def __init__(self, locals=None, encoding=None): def reset_running_time(self): self.running_time = 0 - def runsource(self, source, filename='', symbol='single', + def runsource(self, source, filename=None, symbol='single', encode=True): """Execute Python code. @@ -104,6 +104,8 @@ def runsource(self, source, filename='', symbol='single', if not py3 and encode: source = u'# coding: %s\n%s' % (self.encoding, source) source = source.encode(self.encoding) + if filename is None: + filename = filename_for_console_input(source) with self.timer: return code.InteractiveInterpreter.runsource(self, source, filename, symbol) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index a203accbc..49c01d69a 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -17,6 +17,14 @@ pypy = 'PyPy' in sys.version +def _last_console_filename(): + """Returns the last 'filename' used for console input + (as will be displayed in a traceback).""" + import linecache + try: + return '' % (len(linecache.cache.bpython_history) - 1) + except AttributeError: + return '' class TestInterpreter(unittest.TestCase): def test_syntaxerror(self): @@ -30,11 +38,11 @@ def append_to_a(message): i.runsource('1.1.1.1') if pypy: - expected = ' File ' + green('""') + ', line ' + \ + expected = ' File ' + green('"%s"' % _last_console_filename()) + ', line ' + \ bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + \ bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n' else: - expected = ' File ' + green('""') + ', line ' + \ + expected = ' File ' + green('"%s"' % _last_console_filename()) + ', line ' + \ bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + \ bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n' @@ -56,7 +64,7 @@ def f(): def g(): return f() - i.runsource('g()') + i.runsource('g()', encode=False) if pypy: global_not_found = "global name 'g' is not defined" @@ -64,8 +72,8 @@ def g(): global_not_found = "name 'g' is not defined" expected = 'Traceback (most recent call last):\n File ' + \ - green('""') + ', line ' + bold(magenta('1')) + ', in ' + \ - cyan('') + '\n' + bold(red('NameError')) + ': ' + \ + green('"%s"' % _last_console_filename()) + ', line ' + bold(magenta('1')) + ', in ' + \ + cyan('') + '\n g()\n' + bold(red('NameError')) + ': ' + \ cyan(global_not_found) + '\n' self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) @@ -106,3 +114,11 @@ def test_runsource_unicode(self): i.runsource("a = u'\xfe'", encode=True) self.assertIsInstance(i.locals['a'], type(u'')) self.assertEqual(i.locals['a'], u"\xfe") + + def test_getsource_works_on_interactively_defined_functions(self): + source = 'def foo(x):\n return x + 1\n' + i = interpreter.Interp() + i.runsource(source) + import inspect + inspected_source = inspect.getsource(i.locals['foo']) + self.assertEquals(inspected_source, source) From 7ca380d12e3e5b56653d5a321991b29dd87f63e7 Mon Sep 17 00:00:00 2001 From: Michael Mulley Date: Wed, 25 Mar 2015 17:40:46 -0400 Subject: [PATCH 0562/1650] Move linecache code to new file, use super() --- bpython/history.py | 72 ------------------------------------ bpython/patch_linecache.py | 75 ++++++++++++++++++++++++++++++++++++++ bpython/repl.py | 3 +- 3 files changed, 77 insertions(+), 73 deletions(-) create mode 100644 bpython/patch_linecache.py diff --git a/bpython/history.py b/bpython/history.py index a6f632f08..e08ec2684 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -25,7 +25,6 @@ from __future__ import unicode_literals import io -import linecache import os import stat from itertools import islice @@ -233,74 +232,3 @@ def append_reload_and_write(self, s, filename, encoding): # Make sure that entries contains at least one element. If the # file and s are empty, this can occur. self.entries = [''] - -class BPythonLinecache(dict): - """Replaces the cache dict in the standard-library linecache module, - to also remember (in an unerasable way) bpython console input.""" - - def __init__(self, *args, **kwargs): - dict.__init__(self, *args, **kwargs) - self.bpython_history = [] - - def is_bpython_filename(self, fname): - try: - return fname.startswith('' diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py new file mode 100644 index 000000000..9bd935b50 --- /dev/null +++ b/bpython/patch_linecache.py @@ -0,0 +1,75 @@ +import linecache + +class BPythonLinecache(dict): + """Replaces the cache dict in the standard-library linecache module, + to also remember (in an unerasable way) bpython console input.""" + + def __init__(self, *args, **kwargs): + super(BPythonLinecache, self).__init__(*args, **kwargs) + self.bpython_history = [] + + def is_bpython_filename(self, fname): + try: + return fname.startswith('' diff --git a/bpython/repl.py b/bpython/repl.py index d71aba2c3..877bafc53 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -47,8 +47,9 @@ from bpython.clipboard import get_clipboard, CopyFailed from bpython.config import getpreferredencoding from bpython.formatter import Parenthesis -from bpython.history import History, filename_for_console_input +from bpython.history import History from bpython.paste import PasteHelper, PastePinnwand, PasteFailed +from bpython.patch_linecache import filename_for_console_input from bpython.translations import _, ngettext From 92e9b0b3b8b8a528af94a085df5e7a23df1697e2 Mon Sep 17 00:00:00 2001 From: Michael Mulley Date: Wed, 25 Mar 2015 17:46:36 -0400 Subject: [PATCH 0563/1650] No need to catch this AttributeError in test code --- bpython/test/test_interpreter.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 49c01d69a..43118096f 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals +import linecache import sys try: @@ -20,11 +21,7 @@ def _last_console_filename(): """Returns the last 'filename' used for console input (as will be displayed in a traceback).""" - import linecache - try: - return '' % (len(linecache.cache.bpython_history) - 1) - except AttributeError: - return '' + return '' % (len(linecache.cache.bpython_history) - 1) class TestInterpreter(unittest.TestCase): def test_syntaxerror(self): From 80765a8017973c643566ab64f76371fa5bc4e126 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 20 Apr 2015 22:08:50 +0200 Subject: [PATCH 0564/1650] Fix bpdb name --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e34a95981..14151fabb 100755 --- a/setup.py +++ b/setup.py @@ -247,7 +247,7 @@ def initialize_options(self): 'bpython = bpython.curtsies:main', 'bpython-curses = bpython.cli:main', 'bpython-urwid = bpython.urwid:main [urwid]', - 'bpbd = bpdb:main' + 'bpdb = bpdb:main' ] } From bb1389adfaefa125f5637320229295a62c3d74f7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 20 Apr 2015 23:37:59 +0200 Subject: [PATCH 0565/1650] Add Keywords --- data/bpython.desktop | 1 + 1 file changed, 1 insertion(+) diff --git a/data/bpython.desktop b/data/bpython.desktop index bdb121590..d28912bcc 100644 --- a/data/bpython.desktop +++ b/data/bpython.desktop @@ -7,3 +7,4 @@ Terminal=true Type=Application Categories=Development;Utility;ConsoleOnly; StartupNotify=true +Keywords=Python;REPL;interpreter; From 9fbb325b8cdd1fb3292cf10d60a4ac0fa71849f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 26 Apr 2015 16:48:20 +0200 Subject: [PATCH 0566/1650] Test Python 3.5 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index fa6d1945b..ab0053adb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ python: - "2.7" - "3.3" - "3.4" + - "3.5" - "pypy" - "pypy3" From bdc1281b4ddbe15af472c49e935f031d18bf9b94 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 26 Apr 2015 21:25:02 +0200 Subject: [PATCH 0567/1650] Install pyOpenSSL, pyasn1 and ndg-httpsclient (fixes: #523, #524) Depending on requests[security] doesn't seem to work. Signed-off-by: Sebastian Ramacher --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 14151fabb..db50b5f7a 100755 --- a/setup.py +++ b/setup.py @@ -229,7 +229,9 @@ def initialize_options(self): 'python_full_version == "2.7.4" or ' \ 'python_full_version == "2.7.5" or ' \ 'python_full_version == "2.7.6"': [ - 'requests[security]' + 'pyOpenSSL', + 'pyasn1', + 'ndg-httpsclient' ] } From a33048d500bdad5dd7232ac6f4d117fc9df8dd32 Mon Sep 17 00:00:00 2001 From: thekthuser Date: Sun, 26 Apr 2015 16:56:07 -0400 Subject: [PATCH 0568/1650] fixed bug #506 --- bpython/args.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bpython/args.py b/bpython/args.py index 6fd2b519c..91c8fc7fe 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -3,9 +3,10 @@ """ from __future__ import print_function +import code +import imp import os import sys -import code from optparse import OptionParser, OptionGroup from bpython import __version__ @@ -114,6 +115,9 @@ def exec_code(interpreter, args): source = sourcefile.read() old_argv, sys.argv = sys.argv, args sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) + mod = imp.new_module('__console__') + sys.modules['__console__'] = mod + interpreter.locals = mod.__dict__ interpreter.locals['__file__'] = args[0] interpreter.runsource(source, args[0], 'exec') sys.argv = old_argv From fa3ad3a2708bcc0b1c385b8b4328b09c8f4360e4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Apr 2015 23:03:16 +0200 Subject: [PATCH 0569/1650] Display exception from subprocess Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 877bafc53..7c479f605 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1001,9 +1001,7 @@ def open_in_external_editor(self, filename): editor_args = shlex.split(prepare_for_exec(self.config.editor, encoding)) args = editor_args + [prepare_for_exec(filename, encoding)] - if subprocess.call(args) == 0: - return True - return False + return subprocess.call(args) == 0: def edit_config(self): if not (os.path.isfile(self.config.config_path)): @@ -1028,11 +1026,12 @@ def edit_config(self): else: return False - if self.open_in_external_editor(self.config.config_path): - self.interact.notify(_('bpython config file edited. Restart ' - 'bpython for changes to take effect.')) - else: - self.interact.notify(_('Error editing config file.')) + try: + if self.open_in_external_editor(self.config.config_path): + self.interact.notify(_('bpython config file edited. Restart ' + 'bpython for changes to take effect.')) + except OSError as e: + self.interact.notify(_('Error editing config file: %s') % e) def next_indentation(line, tab_length): From fb07d9694d3d224983e2ecabbea036e73087e30e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Apr 2015 23:13:20 +0200 Subject: [PATCH 0570/1650] Fix SyntaxError Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 7c479f605..184a6665e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1001,7 +1001,7 @@ def open_in_external_editor(self, filename): editor_args = shlex.split(prepare_for_exec(self.config.editor, encoding)) args = editor_args + [prepare_for_exec(filename, encoding)] - return subprocess.call(args) == 0: + return subprocess.call(args) == 0 def edit_config(self): if not (os.path.isfile(self.config.config_path)): From 6e69e89d1e99187b841ed4fa5fd26d39a88b430b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Apr 2015 23:17:09 +0200 Subject: [PATCH 0571/1650] Fix SyntaxError Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 184a6665e..64673b5d5 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1030,8 +1030,8 @@ def edit_config(self): if self.open_in_external_editor(self.config.config_path): self.interact.notify(_('bpython config file edited. Restart ' 'bpython for changes to take effect.')) - except OSError as e: - self.interact.notify(_('Error editing config file: %s') % e) + except OSError as e: + self.interact.notify(_('Error editing config file: %s') % e) def next_indentation(line, tab_length): From c76588768458ca72d52ce34d23b5988d55b3e1cc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Apr 2015 23:35:56 +0200 Subject: [PATCH 0572/1650] Run directories containing __main__.py (fixes #530) Signed-off-by: Sebastian Ramacher --- bpython/args.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 6fd2b519c..95590bdd7 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -4,6 +4,7 @@ from __future__ import print_function import os +import os.path import sys import code from optparse import OptionParser, OptionGroup @@ -110,10 +111,15 @@ def exec_code(interpreter, args): Helper to execute code in a given interpreter. args should be a [faked] sys.argv """ - with open(args[0], 'r') as sourcefile: + plusmain = os.path.join(args[0], '__main__.py') + if os.path.isdir(args[0]) and os.path.exists(plusmain): + path = plusmain + else: + path = args[0] + with open(path, 'r') as sourcefile: source = sourcefile.read() old_argv, sys.argv = sys.argv, args - sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) - interpreter.locals['__file__'] = args[0] - interpreter.runsource(source, args[0], 'exec') + sys.path.insert(0, os.path.abspath(os.path.dirname(path))) + interpreter.locals['__file__'] = path + interpreter.runsource(source, path, 'exec') sys.argv = old_argv From c83d08dc9c668a54f8fc20624195f6d8519068c3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 30 Apr 2015 16:33:35 +0200 Subject: [PATCH 0573/1650] Revert "Run directories containing __main__.py (fixes #530)" This reverts commit c76588768458ca72d52ce34d23b5988d55b3e1cc. --- bpython/args.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 95590bdd7..6fd2b519c 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -4,7 +4,6 @@ from __future__ import print_function import os -import os.path import sys import code from optparse import OptionParser, OptionGroup @@ -111,15 +110,10 @@ def exec_code(interpreter, args): Helper to execute code in a given interpreter. args should be a [faked] sys.argv """ - plusmain = os.path.join(args[0], '__main__.py') - if os.path.isdir(args[0]) and os.path.exists(plusmain): - path = plusmain - else: - path = args[0] - with open(path, 'r') as sourcefile: + with open(args[0], 'r') as sourcefile: source = sourcefile.read() old_argv, sys.argv = sys.argv, args - sys.path.insert(0, os.path.abspath(os.path.dirname(path))) - interpreter.locals['__file__'] = path - interpreter.runsource(source, path, 'exec') + sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) + interpreter.locals['__file__'] = args[0] + interpreter.runsource(source, args[0], 'exec') sys.argv = old_argv From 87ce63e248768d7314a0b2eccb51d2691d1a1310 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 30 Apr 2015 18:39:39 +0200 Subject: [PATCH 0574/1650] Remove useless assert Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 64673b5d5..8536d4fab 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -636,7 +636,6 @@ def complete(self, tab=False): return completer.shown_before_tab else: - assert len(matches) > 1 return tab or completer.shown_before_tab def format_docstring(self, docstring, width, height): From 68cae80f47d5eb1d20f65bb5e1b402bee2d3a379 Mon Sep 17 00:00:00 2001 From: Steven Leiva Date: Sun, 26 Apr 2015 15:25:10 -0400 Subject: [PATCH 0575/1650] Update README.rst --- README.rst | 242 +++++++++++++++++++++++++++-------------------------- 1 file changed, 123 insertions(+), 119 deletions(-) diff --git a/README.rst b/README.rst index bf5fb6e71..8a67c21ed 100644 --- a/README.rst +++ b/README.rst @@ -2,12 +2,87 @@ .. |ImageLink| image:: https://travis-ci.org/bpython/bpython.svg?branch=master .. _ImageLink: https://travis-ci.org/bpython/bpython -bpython - A fancy curses interface to the Python interactive interpreter -======================================================================== +*********************************************************************** +bpython: A fancy curses interface to the Python interactive interpreter +*********************************************************************** + +`bpython`_ is a lightweight Python interpreter that adds several features common +to IDEs. These features include **syntax highlighting**, **expected parameter +list**, **auto-indentation**, and **autocompletion**. (See below for example +usage). + +.. image:: http://i.imgur.com/jf8mCtP.gif + :alt: bpython + :width: 646 + :height: 300 + :align: center + +bpython does **not** aim to be a complete IDE - the focus is on implementing a +few ideas in a practical, useful, and lightweight manner. + +bpython is a great replacement to any occassion where you would normally use the +vanilla Python interpreter - testing out solutions to people's problems on IRC, +quickly testing a method of doing something without creating a temporary file, +etc.. + +You can find more about bpython - including `full documentation`_ - at our +`homepage`_. + +.. contents:: + :local: + :depth: 1 + :backlinks: none + +========================== +Installation & Basic Usage +========================== +If you have `pip`_ installed, you can simply run: + +.. code-block:: bash + + $ pip install bpython + +Start bpython by typing ``bpython`` in your terminal. You can exit bpython by +using the ``exit()`` command. + +=================== +Features & Examples +=================== +* In-line syntax highlighting. This uses Pygments for lexing the code as you + type, and colours appropriately. + +* Readline-like autocomplete. Suggestions displayed as you type. + +* Expected parameter list. As in a lot of modern IDEs, bpython will attempt to + display a list of parameters for any function you call. The inspect module is + tried first, which works with any Python function, and then pydoc if that + fails. + +* Rewind. This isn't called "Undo" because it would be misleading, but "Rewind" + is probably as bad. The idea is that the code entered is kept in memory and + when the Rewind function is called, the last line is popped and the entire + code is re-evaluated. + +* Pastebin code/write to file. Use the key to upload the screen's contents + to pastebin, with a URL returned. + +* Flush curses screen to stdout. When you quit bpython, the screen data will be + flushed to stdout, so it basically looks the same as if you had quit the + vanilla interpreter. + +============= +Configuration +============= +See the sample-config file for a list of available options. You should save +your config file as **~/.config/bpython/config** (i.e +``$XDG_CONFIG_HOME/bpython/config``) or specify at the command line:: + + bpython --config /path/to/bpython/config + +============ Dependencies ============ - * Pygments * requests * curtsies >= 0.1.18 @@ -18,14 +93,17 @@ Dependencies * watchdog (optional, for monitoring imported modules for changes) * jedi (optional, for experimental multiline completion) -If you are using Python 2 before 2.7.7, the following dependency is also +Python 2 before 2.7.7 +--------------------- +If you are using Python 2 before 2.7.7, the followign dependency is also required: * requests[security] -If you have problems installing cffi which is needed by pyOpenSSL, -please take a look at https://cffi.readthedocs.org/en/release-0.8/#macos-x. - +cffi +---- +If you have problems installing cffi, which is needed by OpenSSL, please take a +look at `cffi docs`_. bpython-urwid ------------- @@ -33,140 +111,66 @@ bpython-urwid * urwid -Introduction -============ -A few people asked for stuff like syntax highlighting and autocomplete for the -Python interactive interpreter. IPython seems to offer this (plus you can get -readline behaviour in the vanilla interpreter) but I tried IPython a couple of -times. Perhaps I didn't really get it, but I get the feeling that the ideas -behind IPython are pretty different to bpython. I didn't want to create a whole -development environment; I simply wanted to provide a couple of neat features -that already exist and turn them into something a little more interactive. - -The idea is to provide the user with all the features in-line, much like modern -IDEs, but in a simple, lightweight package that can be run in a terminal -window, so curses seemed like the best choice. Sorry if you use Windows. - -bpython doesn't attempt to create anything new or groundbreaking, it simply -brings together a few neat ideas and focuses on practicality and usefulness. -For this reason, the "Rewind" function should be taken with a pinch of salt, -but personally I have found it to be very useful. I use bpython now whenever I -would normally use the vanilla interpreter, e.g. for testing out solutions to -people's problems on IRC, quickly testing a method of doing something without -creating a temporary file, etc.. - -I hope you find it useful and please feel free to submit any bugs/patches (yeah -right)/suggestions to: -robertanthonyfarrell@gmail.com -or place them at the github issue page for this project at: -http://github.com/bpython/bpython/issues/ +========== +Known Bugs +========== +For known bugs please see bpython's `known issues and FAQ`_ page. -For any other ways of communicating with bpython users and devs you can find us -at the community page on the projects homepage: -http://bpython-interpreter.org/community +====================== +Contact & Contributing +====================== +I hope you find it useful and please feel free to submit any bugs/patches +suggestions to `Robert`_ or place them on the github +`issues tracker`_. -Or in the documentation at http://docs.bpython-interpreter.org/community.html. +For any other ways of communicating with bpython users and devs you can find us +at the community page on the `project homepage`_, or in the `community`_. Hope to see you there! -Features -======== - -* In-line syntax highlighting. - This uses Pygments for lexing the code as you type, and colours - appropriately. Pygments does a great job of doing all of the tricky stuff - and really leaving me with very little to do except format the tokens in - all my favourite colours. - -* Readline-like autocomplete with suggestions displayed as you type. - Thanks to Python's readline interface to libreadline and a ready-made class - for using a Python interpreter's scope as the dataset, the only work here - was displaying the readline matches as you type in a separate curses window - below/above the cursor. - -* Expected parameter list. - As in a lot of modern IDEs, bpython will attempt to display a list of - parameters for any function you call. The inspect module is tried first, - which works with any Python function, and then pydoc if that fails, which - seems to be pretty adequate, but obviously in some cases it's simply not - possible. I used pyparsing to cure my nested parentheses woes; again, it - was nice and easy. - -* Rewind. - I didn't call this "Undo" because I thought that would be misleading, but - "Rewind" is probably as bad. The idea is that the code entered is kept in - memory and when the Rewind function is called, the last line is popped and - the entire code is re-evaluated. As you can imagine, this has a lot of - potential problems, but for defining classes and functions, I've found it - to be nothing but useful. - -* Pastebin code/write to file. - I don't really use the save thing much, but the pastebin thing's great. Hit - a key and what you see on the screen will be sent to a pastebin and a URL - is returned for you to do what you like with. I've hardcoded - paste.pocoo.org in for now, that needs to be fixed so it's configurable. - Next release, I promise. - -* Flush curses screen to stdout. - A featurette, perhaps, but I thought it was worth noting. I can't - personally recall a curses app that does this, perhaps it's often not - useful, but when you quit bpython, the screen data will be flushed to - stdout, so it basically looks the same as if you had quit the vanilla - interpreter. - -Configuration -============= -See the sample-config file for a list of available options. You should save -your config file as ~/.config/bpython/config (i.e -$XDG_CONFIG_HOME/bpython/config) or specify at the command line:: - - bpython --config /path/to/bpython/config - -Known Bugs -========== -For known bugs please see bpython's issue tracker at github: - -http://github.com/bpython/bpython/issues/ - +=================== CLI Windows Support =================== Dependencies ------------ -Curses - Use the appropriate version compiled by Christoph Gohlke - http://www.lfd.uci.edu/~gohlke/pythonlibs/ +`Curses`_ Use the appropriate version compiled by Christoph Gohlke. -pyreadline - Use the version in the cheeseshop - http://pypi.python.org/pypi/pyreadline/ +`pyreadline`_ Use the version in the cheeseshop. +=========== Recommended ------------ +=========== Obtain the less program from GnuUtils. This makes the pager work as intended. It can be obtained from cygwin or GnuWin32 or msys +============================== Current version is tested with ------------------------------- - * Curses 2.2 - * pyreadline 1.7 +============================== +* Curses 2.2 +* pyreadline 1.7 +============ Curses Notes ------------- +============ The curses used has a bug where the colours are displayed incorrectly: - * red is swapped with blue - * cyan is swapped with yellow +* red is swapped with blue +* cyan is swapped with yellow -To correct this I have provided my windows.theme file. +To correct this I have provided a windows.theme file. This curses implementation has 16 colors (dark and light versions of the colours) -See also -======== - -Documentation - http://docs.bpython-interpreter.org/ - -Developer documentation - http://docs.bpython-interpreter.org/contributing.html +.. _homepage: http://www.bpython-interpreter.org +.. _full documentation: http://docs.bpython-interpreter.org/ +.. _cffi docs: https://cffi.readthedocs.org/en/release-0.8/#macos-x +.. _issues tracker: http://github.com/bpython/bpython/issues/ +.. _pip: https://pip.pypa.io/en/latest/index.html +.. _project homepage: http://bpython-interpreter.org/community +.. _community: http://docs.bpython-interpreter.org/community.html +.. _Robert: robertanthonyfarrell@gmail.com +.. _bpython: http://www.bpython-interpreter.org/ +.. _Curses: http://www.lfd.uci.edu/~gohlke/pythonlibs/ +.. _pyreadline: http://pypi.python.org/pypi/pyreadline/ +.. _known issues and FAQ: http://bpython-interpreter.org/known-issues-and-faq.html From e898411c03315c67030364283ac75ed0da180b8b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 May 2015 00:35:14 +0200 Subject: [PATCH 0576/1650] Add missing new line --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 8a67c21ed..aae559515 100644 --- a/README.rst +++ b/README.rst @@ -154,6 +154,7 @@ Current version is tested with Curses Notes ============ The curses used has a bug where the colours are displayed incorrectly: + * red is swapped with blue * cyan is swapped with yellow From 24e5c69497a06bb983589919d410fec74b7cf04a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 May 2015 20:16:41 +0200 Subject: [PATCH 0577/1650] Failing test for #536 --- bpython/test/test_autocomplete.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index a6f2f153a..7d86569b8 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -251,6 +251,16 @@ def test_att_matches_found_on_old_style_class_object(self): self.assertIn(u'A.__dict__', self.com.matches(3, 'A._', locals_={'A': OldStyleFoo})) + @skip_old_style + @unittest.expectedFailure + def test_issue536(self): + class OldStyleWithBrokenGetAttr: + def __getattr__(self, attr): + raise Exception() + + locals_ = {'a': OldStyleWithBrokenGetAttr()} + self.com.matches(2, 'a.', locals_=locals_) + class TestMagicMethodCompletion(unittest.TestCase): From e74bbd20644df265a85549f046e8fa11271faba2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 May 2015 20:18:02 +0200 Subject: [PATCH 0578/1650] Move if py3 to module level Signed-off-by: Sebastian Ramacher --- bpython/inspection.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index b188dc6c7..e0cc140df 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -286,8 +286,11 @@ def get_encoding_file(fname): return 'ascii' -def get_source_unicode(obj): - """Returns a decoded source of object""" - if py3: +if py3: + def get_source_unicode(obj): + """Returns a decoded source of object""" return inspect.getsource(obj) - return inspect.getsource(obj).decode(get_encoding(obj)) +else: + def get_source_unicode(obj): + """Returns a decoded source of object""" + return inspect.getsource(obj).decode(get_encoding(obj)) From 674c3bfbd35853001850b5a1c610b4e3c3a71bba Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 May 2015 20:57:47 +0200 Subject: [PATCH 0579/1650] Add workaround for old-style class with broken __getattr__ (fixes #536) Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 19 ++++++++++++++++++- bpython/test/test_autocomplete.py | 4 ++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a65ed913c..3a009c38a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -296,7 +296,7 @@ def attr_lookup(self, obj, expr, attr): """Second half of original attr_matches method factored out so it can be wrapped in a safe try/finally block in case anything bad happens to restore the original __getattribute__ method.""" - words = dir(obj) + words = self.list_attributes(obj) if hasattr(obj, '__class__'): words.append('__class__') words = words + rlcompleter.get_class_members(obj.__class__) @@ -317,6 +317,23 @@ def attr_lookup(self, obj, expr, attr): matches.append("%s.%s" % (expr, word)) return matches + if py3: + def list_attributes(self, obj): + return dir(obj) + else: + def list_attributes(self, obj): + if isinstance(obj, InstanceType): + try: + return dir(obj) + except Exception: + # This is a case where we can not prevent user code from + # running. We return a default list attributes on error + # instead. (#536) + return ['__doc__', '__module__'] + else: + return dir(obj) + + class DictKeyCompletion(BaseCompletionType): diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 7d86569b8..bfe1b0b66 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -252,14 +252,14 @@ def test_att_matches_found_on_old_style_class_object(self): self.com.matches(3, 'A._', locals_={'A': OldStyleFoo})) @skip_old_style - @unittest.expectedFailure def test_issue536(self): class OldStyleWithBrokenGetAttr: def __getattr__(self, attr): raise Exception() locals_ = {'a': OldStyleWithBrokenGetAttr()} - self.com.matches(2, 'a.', locals_=locals_) + self.assertIn(u'a.__module__', + self.com.matches(3, 'a._', locals_=locals_)) class TestMagicMethodCompletion(unittest.TestCase): From 206b0c5601577bf459b4a55ae45f78a5634ca147 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 13:13:34 +0200 Subject: [PATCH 0580/1650] Remove unused method Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 3a009c38a..8510abba5 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -136,9 +136,6 @@ def shown_before_tab(self): once that has happened.""" return self._shown_before_tab - def method_match(self, word, size, text): - return word[:size] == text - class CumulativeCompleter(BaseCompletionType): """Returns combined matches from several completers""" From 360224229eea6cd3cf2de3c64cd2ae13367e5578 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 13:13:46 +0200 Subject: [PATCH 0581/1650] Move regex definition --- bpython/autocomplete.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 8510abba5..1d0a00060 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -223,11 +223,10 @@ def format(self, filename): return filename -attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") - - class AttrCompletion(BaseCompletionType): + attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") + def matches(self, cursor_offset, line, **kwargs): if 'locals_' not in kwargs: return None @@ -272,7 +271,7 @@ def attr_matches(self, text, namespace): # Gna, Py 2.6's rlcompleter searches for __call__ inside the # instance instead of the type, so we monkeypatch to prevent # side-effects (__getattr__/__getattribute__) - m = attr_matches_re.match(text) + m = self.attr_matches_re.match(text) if not m: return [] From a8787b350a9f647317150d2b5b2877c8429b8c77 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 13:14:08 +0200 Subject: [PATCH 0582/1650] Decode before comparing Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 1d0a00060..d690e3e4e 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -402,11 +402,11 @@ def matches(self, cursor_offset, line, **kwargs): matches.add(word) for nspace in (builtins.__dict__, locals_): for word, val in iteritems(nspace): + word = try_decode(word, 'ascii') + # if identifier isn't ascii, don't complete (syntax error) + if word is None: + continue if self.method_match(word, n, text) and word != "__builtins__": - word = try_decode(word, 'ascii') - # if identifier isn't ascii, don't complete (syntax error) - if word is None: - continue matches.add(_callable_postfix(val, word)) return matches From dbd8066697d41ebb82c604e2c165f902bc68f42c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 13:38:21 +0200 Subject: [PATCH 0583/1650] Clean up unittest imports Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 2 +- bpython/test/test_config.py | 6 +----- bpython/test/test_crashers.py | 5 +---- bpython/test/test_curtsies_coderunner.py | 8 +------- bpython/test/test_curtsies_parser.py | 6 +----- bpython/test/test_curtsies_repl.py | 7 +------ bpython/test/test_filewatch.py | 7 +------ bpython/test/test_history.py | 6 +----- bpython/test/test_importcompletion.py | 6 +----- bpython/test/test_inspection.py | 7 ++----- bpython/test/test_interpreter.py | 9 +++------ bpython/test/test_keys.py | 8 ++------ bpython/test/test_line_properties.py | 6 +----- bpython/test/test_manual_readline.py | 6 +----- bpython/test/test_preprocess.py | 10 +++------- bpython/test/test_repl.py | 8 +++----- 16 files changed, 24 insertions(+), 83 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index d690e3e4e..7e89f4a66 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -488,7 +488,7 @@ def matches(self, cursor_offset, line, **kwargs): self._orig_start = None return None except IndexError: - # for https://github.com/bpython/bpython/issues/483 + # for #483 self._orig_start = None return None diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index 16def998f..6b8c5dea4 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -2,11 +2,7 @@ import tempfile import textwrap -try: - import unittest2 as unittest -except ImportError: - import unittest - +from bpython.test import unittest from bpython import config TEST_THEME_PATH = os.path.join(os.path.dirname(__file__), "test.theme") diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index d07195929..5db661bbf 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -6,10 +6,7 @@ import termios import textwrap -try: - import unittest2 as unittest -except ImportError: - import unittest +from bpython.test import unittest try: from twisted.internet import reactor diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index 2446ecc65..80af72760 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -1,12 +1,6 @@ import sys -from bpython.test import mock - -try: - import unittest2 as unittest -except ImportError: - import unittest - +from bpython.test import mock, unittest from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput diff --git a/bpython/test/test_curtsies_parser.py b/bpython/test/test_curtsies_parser.py index 4b2c96e00..0d765bab2 100644 --- a/bpython/test/test_curtsies_parser.py +++ b/bpython/test/test_curtsies_parser.py @@ -1,10 +1,6 @@ from __future__ import unicode_literals -try: - import unittest2 as unittest -except ImportError: - import unittest - +from bpython.test import unittest from bpython.curtsiesfrontend import parse from curtsies.fmtfuncs import yellow, cyan, green, bold diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index fe2363914..5f85825e0 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -9,11 +9,6 @@ from contextlib import contextmanager from six.moves import StringIO -try: - import unittest2 as unittest -except ImportError: - import unittest - from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter from bpython.curtsiesfrontend import events as bpythonevents @@ -22,7 +17,7 @@ from bpython import args from bpython._py3compat import py3 from bpython.test import (FixLanguageTestCase as TestCase, MagicIterMock, mock, - builtin_target) + builtin_target, unittest) def setup_config(conf): diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index 5f94f40ec..e9e08449e 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -1,10 +1,5 @@ import os -try: - import unittest2 as unittest -except ImportError: - import unittest - try: import watchdog from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler @@ -12,7 +7,7 @@ except ImportError: has_watchdog = False -from bpython.test import mock +from bpython.test import mock, unittest @unittest.skipUnless(has_watchdog, "watchdog required") class TestModuleChangeEventHandler(unittest.TestCase): diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index e32431d06..abbe99d1f 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -1,11 +1,7 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest - from six.moves import range from bpython.history import History +from bpython.test import unittest class TestHistory(unittest.TestCase): diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index cd301f504..4df736b54 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -1,11 +1,7 @@ from __future__ import unicode_literals from bpython import importcompletion - -try: - import unittest2 as unittest -except ImportError: - import unittest +from bpython.test import unittest class TestSimpleComplete(unittest.TestCase): diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 35eea43be..b91f0ffbb 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -2,16 +2,13 @@ import os -try: - import unittest2 as unittest -except ImportError: - import unittest - from bpython import inspection +from bpython.test import unittest from bpython.test.fodder import encoding_ascii from bpython.test.fodder import encoding_latin1 from bpython.test.fodder import encoding_utf8 + foo_ascii_only = u'''def foo(): """Test""" pass diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 43118096f..c0b9bc50b 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -5,24 +5,21 @@ import linecache import sys -try: - import unittest2 as unittest -except ImportError: - import unittest - from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain from bpython.curtsiesfrontend import interpreter from bpython._py3compat import py3 -from bpython.test import mock +from bpython.test import mock, unittest pypy = 'PyPy' in sys.version + def _last_console_filename(): """Returns the last 'filename' used for console input (as will be displayed in a traceback).""" return '' % (len(linecache.cache.bpython_history) - 1) + class TestInterpreter(unittest.TestCase): def test_syntaxerror(self): i = interpreter.Interp() diff --git a/bpython/test/test_keys.py b/bpython/test/test_keys.py index 3fb1f3663..bf15542dd 100644 --- a/bpython/test/test_keys.py +++ b/bpython/test/test_keys.py @@ -1,9 +1,5 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest - -import bpython.keys as keys +from bpython import keys +from bpython.test import unittest class TestCLIKeys(unittest.TestCase): diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 2f83d7175..26eee9a07 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -1,10 +1,6 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest - import re +from bpython.test import unittest from bpython.line import current_word, current_dict_key, current_dict, \ current_string, current_object, current_object_attribute, \ current_from_import_from, current_from_import_import, current_import, \ diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index d82f96d38..6ef610638 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -1,14 +1,10 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest - from bpython.curtsiesfrontend.manual_readline import \ left_arrow, right_arrow, beginning_of_line, forward_word, back_word, \ end_of_line, delete, last_word_pos, backspace, delete_from_cursor_back, \ delete_from_cursor_forward, delete_rest_of_word, delete_word_to_cursor, \ transpose_character_before_cursor, UnconfiguredEdits, \ delete_word_from_cursor_back +from bpython.test import unittest class TestManualReadline(unittest.TestCase): diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index 526f91c72..f94366565 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -4,17 +4,13 @@ import inspect import re -try: - import unittest2 as unittest -except ImportError: - import unittest -skip = unittest.skip - from bpython.curtsiesfrontend.interpreter import code_finished_will_parse from bpython.curtsiesfrontend.preprocess import preprocess +from bpython.test import unittest +from bpython.test.fodder import original, processed -from bpython.test.fodder import original as original, processed +skip = unittest.skip preproc = partial(preprocess, compiler=compiler) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 7e0e7aaa8..2299cea42 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -8,17 +8,15 @@ from six.moves import range import sys -try: - import unittest2 as unittest -except ImportError: - import unittest - from bpython._py3compat import py3 from bpython import config, repl, cli, autocomplete from bpython.test import MagicIterMock, mock, FixLanguageTestCase as TestCase +from bpython.test import unittest + pypy = 'PyPy' in sys.version + def setup_config(conf): config_struct = config.Struct() config.loadini(config_struct, os.devnull) From dd0a489d96a68168012b7186861f063e7114f361 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 16:20:08 +0200 Subject: [PATCH 0584/1650] Clean up imports Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/interpreter.py | 4 +--- bpython/curtsiesfrontend/repl.py | 3 +-- bpython/test/test_repl.py | 8 +++----- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index fea3fddcc..5f325a84c 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,5 +1,3 @@ -import code -import traceback import sys from codeop import CommandCompiler from six import iteritems @@ -13,7 +11,7 @@ from bpython.curtsiesfrontend.parse import parse from bpython.repl import Interpreter as ReplInterpreter from bpython.config import getpreferredencoding -from bpython._py3compat import py3 + default_colors = { Generic.Error: 'R', diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f4da90230..b460b70f5 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -3,7 +3,6 @@ import contextlib import errno -import functools import greenlet import logging import os @@ -15,7 +14,7 @@ import threading import time import unicodedata -from six.moves import range, builtins +from six.moves import range from pygments import format from bpython._py3compat import PythonLexer diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 2299cea42..710d07c15 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,12 +1,12 @@ from itertools import islice +from six.moves import range import collections import inspect import os import shutil import socket -import tempfile -from six.moves import range import sys +import tempfile from bpython._py3compat import py3 from bpython import config, repl, cli, autocomplete @@ -65,7 +65,7 @@ def test_next(self): next(self.matches_iterator) self.assertEqual(next(self.matches_iterator), self.matches[0]) - self.assertEqual(next(self.matches_iterator), self. matches[1]) + self.assertEqual(next(self.matches_iterator), self.matches[1]) self.assertNotEqual(next(self.matches_iterator), self.matches[1]) def test_previous(self): @@ -249,8 +249,6 @@ def test_current_function_cpython(self): self.assert_get_source_error_for_current_function( collections.defaultdict, "could not find class definition") - - def test_current_line(self): self.repl.interp.locals['a'] = socket.socket self.set_input_line('a') From 26b8dafcc906da8c82a9092437d4795f64bb39b2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 16:23:02 +0200 Subject: [PATCH 0585/1650] PEP8 --- bpython/autocomplete.py | 1 - bpython/patch_linecache.py | 6 +++++- bpython/repl.py | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 7e89f4a66..f4a7a0d7d 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -330,7 +330,6 @@ def list_attributes(self, obj): return dir(obj) - class DictKeyCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index 9bd935b50..7dc321e7a 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -1,5 +1,6 @@ import linecache + class BPythonLinecache(dict): """Replaces the cache dict in the standard-library linecache module, to also remember (in an unerasable way) bpython console input.""" @@ -29,7 +30,7 @@ def remember_bpython_input(self, source): a fake filename to use to retrieve it later.""" filename = '' % len(self.bpython_history) self.bpython_history.append((len(source), None, - source.splitlines(True), filename)) + source.splitlines(True), filename)) return filename def __getitem__(self, key): @@ -50,6 +51,7 @@ def __delitem__(self, key): if not self.is_bpython_filename(key): return super(BPythonLinecache, self).__delitem__(key) + def _bpython_clear_linecache(): try: bpython_history = linecache.cache.bpython_history @@ -58,11 +60,13 @@ def _bpython_clear_linecache(): linecache.cache = BPythonLinecache() linecache.cache.bpython_history = bpython_history + # Monkey-patch the linecache module so that we're able # to hold our command history there and have it persist linecache.cache = BPythonLinecache(linecache.cache) linecache.clearcache = _bpython_clear_linecache + def filename_for_console_input(code_string): """Remembers a string of source code, and returns a fake filename to use to retrieve it later.""" diff --git a/bpython/repl.py b/bpython/repl.py index 8536d4fab..6a9c0ebfc 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -766,7 +766,6 @@ def do_pastebin(self, s): self.prev_removal_url), 10) return self.prev_pastebin_url - self.interact.notify(_('Posting data to pastebin...')) try: paste_url, removal_url = self.paster.paste(s) From e952c73eb972d48d02af7e6274a48ab27ff80b9e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 May 2015 16:32:36 +0200 Subject: [PATCH 0586/1650] PEP8 --- bpython/test/test_filewatch.py | 1 + bpython/test/test_repl.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index e9e08449e..061e18b2b 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -9,6 +9,7 @@ from bpython.test import mock, unittest + @unittest.skipUnless(has_watchdog, "watchdog required") class TestModuleChangeEventHandler(unittest.TestCase): diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 710d07c15..57d83b147 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -162,7 +162,6 @@ def test_func_name_method_issue_479(self): self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.current_func.__name__, expected_name) - def test_syntax_error_parens(self): for line in ["spam(]", "spam([)", "spam())"]: self.set_input_line(line) @@ -329,7 +328,8 @@ def test_fuzzy_global_complete(self): self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, ['UnboundLocalError(', '__doc__'] if not py3 else - ['ChildProcessError(', 'UnboundLocalError(', '__doc__']) + ['ChildProcessError(', 'UnboundLocalError(', + '__doc__']) # 2. Attribute tests def test_simple_attribute_complete(self): From bd2002a619f62d643f9328456cf45ec7a1011d14 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 22 May 2015 18:50:55 +0200 Subject: [PATCH 0587/1650] Add 0.14.2 changelog Signed-off-by: Sebastian Ramacher --- CHANGELOG | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0f229b3d6..c8c4bd714 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,17 @@ Fixes: * #484: Switch `bpython.embed` to the curtsies frontend. +0.14.2 +------ + +Fixes: + +* #498: Fixed is_callable +* #509: Fixed fcntl usage. +* #523, #524: Fix conditional dependencies for SNI support again. +* Fix binary name of bpdb. + + 0.14.1 ------ From 66d7b5234fabf224d6f748cd9e3114b996934805 Mon Sep 17 00:00:00 2001 From: noella Date: Thu, 28 May 2015 22:16:45 -0400 Subject: [PATCH 0588/1650] Refactoring with named tuples --- bpython/autocomplete.py | 51 ++++++++++++++++++----------------------- bpython/line.py | 29 ++++++++++++----------- 2 files changed, 37 insertions(+), 43 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index f4a7a0d7d..a172ca4c0 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -2,7 +2,6 @@ # The MIT License # -# Copyright (c) 2009-2015 the bpython authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -39,6 +38,7 @@ from bpython import inspection from bpython import importcompletion from bpython import line as lineparts +from bpython.line import LinePart from bpython._py3compat import py3, try_decode from bpython.lazyre import LazyReCompile @@ -126,8 +126,8 @@ def format(self, word): def substitute(self, cursor_offset, line, match): """Returns a cursor offset and line with match swapped in""" - start, end, word = self.locate(cursor_offset, line) - result = start + len(match), line[:start] + match + line[end:] + lpart = self.locate(cursor_offset, line) + result = lpart.start + len(match), line[:lpart.start] + match + line[lpart.end:] return result @property @@ -200,14 +200,13 @@ def matches(self, cursor_offset, line, **kwargs): cs = lineparts.current_string(cursor_offset, line) if cs is None: return None - start, end, text = cs matches = set() - username = text.split(os.path.sep, 1)[0] + username = cs.word.split(os.path.sep, 1)[0] user_dir = os.path.expanduser(username) - for filename in self.safe_glob(os.path.expanduser(text)): + for filename in self.safe_glob(os.path.expanduser(cs.word)): if os.path.isdir(filename): filename += os.path.sep - if text.startswith('~'): + if cs.word.startswith('~'): filename = username + filename[len(user_dir):] matches.add(filename) return matches @@ -235,25 +234,24 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - text = r[2] if locals_ is None: locals_ = __main__.__dict__ - assert '.' in text + assert '.' in r.word - for i in range(1, len(text) + 1): - if text[-i] == '[': + for i in range(1, len(r.word) + 1): + if r.word[-i] == '[': i -= 1 break - methodtext = text[-i:] - matches = set(''.join([text[:-i], m]) + methodtext = r.word[-i:] + matches = set(''.join([r.word[:-i], m]) for m in self.attr_matches(methodtext, locals_)) # TODO add open paren for methods via _callable_prefix (or decide not # to) unless the first character is a _ filter out all attributes # starting with a _ - if not text.split('.')[-1].startswith('_'): + if not r.word.split('.')[-1].startswith('_'): matches = set(match for match in matches if not match.split('.')[-1].startswith('_')) return matches @@ -340,7 +338,6 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - start, end, orig = r _, _, dexpr = lineparts.current_dict(cursor_offset, line) try: obj = safe_eval(dexpr, locals_) @@ -348,7 +345,7 @@ def matches(self, cursor_offset, line, **kwargs): return set() if isinstance(obj, dict) and obj.keys(): return set("{0!r}]".format(k) for k in obj.keys() - if repr(k).startswith(orig)) + if repr(k).startswith(r.word)) else: return set() @@ -371,8 +368,7 @@ def matches(self, cursor_offset, line, **kwargs): return None if 'class' not in current_block: return None - start, end, word = r - return set(name for name in MAGIC_METHODS if name.startswith(word)) + return set(name for name in MAGIC_METHODS if name.startswith(r.word)) def locate(self, current_offset, line): return lineparts.current_method_definition_name(current_offset, line) @@ -392,12 +388,11 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - start, end, text = r matches = set() - n = len(text) + n = len(r.word) for word in KEYWORDS: - if self.method_match(word, n, text): + if self.method_match(word, n, r.word): matches.add(word) for nspace in (builtins.__dict__, locals_): for word, val in iteritems(nspace): @@ -405,7 +400,7 @@ def matches(self, cursor_offset, line, **kwargs): # if identifier isn't ascii, don't complete (syntax error) if word is None: continue - if self.method_match(word, n, text) and word != "__builtins__": + if self.method_match(word, n, r.word) and word != "__builtins__": matches.add(_callable_postfix(val, word)) return matches @@ -425,14 +420,13 @@ def matches(self, cursor_offset, line, **kwargs): r = self.locate(cursor_offset, line) if r is None: return None - start, end, word = r if argspec: matches = set(name + '=' for name in argspec[1][0] if isinstance(name, string_types) and - name.startswith(word)) + name.startswith(r.word)) if py3: matches.update(name + '=' for name in argspec[1][4] - if name.startswith(word)) + if name.startswith(r.word)) return matches def locate(self, current_offset, line): @@ -446,14 +440,13 @@ def matches(self, cursor_offset, line, **kwargs): if r is None: return None - start, end, word = r attrs = dir('') if not py3: # decode attributes attrs = (att.decode('ascii') for att in attrs) - matches = set(att for att in attrs if att.startswith(word)) - if not word.startswith('_'): + matches = set(att for att in attrs if att.startswith(r.word)) + if not r.word.startswith('_'): return set(match for match in matches if not match.startswith('_')) return matches @@ -513,7 +506,7 @@ def matches(self, cursor_offset, line, **kwargs): def locate(self, cursor_offset, line): start = self._orig_start end = cursor_offset - return start, end, line[start:end] + return LinePart(start, end, line[start:end]) class MultilineJediCompletion(JediCompletion): def matches(self, cursor_offset, line, **kwargs): diff --git a/bpython/line.py b/bpython/line.py index 12f753142..c1da3943b 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -5,10 +5,11 @@ word.""" from itertools import chain +from collections import namedtuple from bpython.lazyre import LazyReCompile - current_word_re = LazyReCompile(r'[\w_][\w0-9._]*[(]?') +LinePart = namedtuple('LinePart', ['start', 'stop', 'word']) def current_word(cursor_offset, line): @@ -25,7 +26,7 @@ def current_word(cursor_offset, line): word = m.group() if word is None: return None - return (start, end, word) + return LinePart(start, end, word) current_dict_key_re = LazyReCompile(r'''[\w_][\w0-9._]*\[([\w0-9._(), '"]*)''') @@ -36,7 +37,7 @@ def current_dict_key(cursor_offset, line): matches = current_dict_key_re.finditer(line) for m in matches: if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: - return (m.start(1), m.end(1), m.group(1)) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -48,7 +49,7 @@ def current_dict(cursor_offset, line): matches = current_dict_re.finditer(line) for m in matches: if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: - return (m.start(1), m.end(1), m.group(1)) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -66,7 +67,7 @@ def current_string(cursor_offset, line): for m in current_string_re.finditer(line): i = 3 if m.group(3) else 4 if m.start(i) <= cursor_offset and m.end(i) >= cursor_offset: - return m.start(i), m.end(i), m.group(i) + return LinePart(m.start(i), m.end(i), m.group(i)) return None @@ -89,7 +90,7 @@ def current_object(cursor_offset, line): s += m.group(1) if not s: return None - return start, start+len(s), s + return LinePart(start, start+len(s), s) current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]?') @@ -106,7 +107,7 @@ def current_object_attribute(cursor_offset, line): for m in matches: if (m.start(1) + start <= cursor_offset and m.end(1) + start >= cursor_offset): - return m.start(1) + start, m.end(1) + start, m.group(1) + return LinePart(m.start(1) + start, m.end(1) + start, m.group(1)) return None @@ -128,7 +129,7 @@ def current_from_import_from(cursor_offset, line): for m in matches: if ((m.start(1) < cursor_offset and m.end(1) >= cursor_offset) or (m.start(2) < cursor_offset and m.end(2) >= cursor_offset)): - return m.start(1), m.end(1), m.group(1) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -153,7 +154,7 @@ def current_from_import_import(cursor_offset, line): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: - return start, end, m.group(1) + return LinePart(start, end, m.group(1)) return None @@ -175,7 +176,7 @@ def current_import(cursor_offset, line): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: - return start, end, m.group(1) + return LinePart(start, end, m.group(1)) current_method_definition_name_re = LazyReCompile("def\s+([a-zA-Z_][\w]*)") @@ -186,7 +187,7 @@ def current_method_definition_name(cursor_offset, line): matches = current_method_definition_name_re.finditer(line) for m in matches: if (m.start(1) <= cursor_offset and m.end(1) >= cursor_offset): - return m.start(1), m.end(1), m.group(1) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -198,7 +199,7 @@ def current_single_word(cursor_offset, line): matches = current_single_word_re.finditer(line) for m in matches: if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: - return m.start(1), m.end(1), m.group(1) + return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -209,7 +210,7 @@ def current_dotted_attribute(cursor_offset, line): return None start, end, word = match if '.' in word[1:]: - return start, end, word + return LinePart(start, end, word) current_string_literal_attr_re = LazyReCompile( @@ -223,5 +224,5 @@ def current_string_literal_attr(cursor_offset, line): matches = current_string_literal_attr_re.finditer(line) for m in matches: if m.start(4) <= cursor_offset and m.end(4) >= cursor_offset: - return m.start(4), m.end(4), m.group(4) + return LinePart(m.start(4), m.end(4), m.group(4)) return None From ab3ed098216f923436f9f54abed0f0f5cd0ae889 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:03:35 -0400 Subject: [PATCH 0589/1650] argspec replaced with named tuples for better readability --- bpython/inspection.py | 48 +++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index e0cc140df..2e3958e87 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -28,6 +28,7 @@ import io import keyword import pydoc +from collections import namedtuple from six.moves import range from pygments.token import Token @@ -40,6 +41,11 @@ _name = LazyReCompile(r'[a-zA-Z_]\w*$') +Argspec = namedtuple('Argspec', ['args', 'varargs', 'varkwargs', 'defaults', + 'kwonly', 'kwonly_defaults', 'annotations']) + +FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method']) + class AttrCleaner(object): """A context manager that tries to make an object not exhibit side-effects @@ -175,44 +181,39 @@ def fixlongargs(f, argspec): def getpydocspec(f, func): try: - argspec = pydoc.getdoc(f) + docstring = pydoc.getdoc(f) except NameError: return None - - s = getpydocspec_re.search(argspec) + s = getpydocspec_re.search(docstring) if s is None: return None if not hasattr(f, '__name__') or s.groups()[0] != f.__name__: return None - args = list() - defaults = list() - varargs = varkwargs = None - kwonly_args = list() - kwonly_defaults = dict() + argspec = Argspec(list(), None, None, list(), list(), dict(), None) + for arg in s.group(2).split(','): arg = arg.strip() if arg.startswith('**'): - varkwargs = arg[2:] + argspec.varkwargs = arg[2:] elif arg.startswith('*'): - varargs = arg[1:] + argspec.varargs = arg[1:] else: arg, _, default = arg.partition('=') - if varargs is not None: - kwonly_args.append(arg) + if argspec.varargs is not None: + argspec.kwonly_args.append(arg) if default: - kwonly_defaults[arg] = default + argspec.kwonly_defaults[arg] = default else: - args.append(arg) + argspec.args.append(arg) if default: - defaults.append(default) + argspec.defaults.append(default) - return [func, (args, varargs, varkwargs, defaults, - kwonly_args, kwonly_defaults)] + return argspec -def getargspec(func, f): +def getfuncprops(func, f): # Check if it's a real bound method or if it's implicitly calling __init__ # (i.e. FooClass(...) and not FooClass.__init__(...) -- the former would # not take 'self', the latter would: @@ -238,16 +239,19 @@ def getargspec(func, f): argspec = list(argspec) fixlongargs(f, argspec) - argspec = [func, argspec, is_bound_method] + if len(argspec) == 4: + argspec = argspec + [list(),dict(),None] + argspec = Argspec(*argspec) + fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError): with AttrCleaner(f): argspec = getpydocspec(f, func) if argspec is None: return None if inspect.ismethoddescriptor(f): - argspec[1][0].insert(0, 'obj') - argspec.append(is_bound_method) - return argspec + argspec.args.insert(0, 'obj') + fprops = FuncProps(func, argspec, is_bound_method) + return fprops def is_eval_safe_name(string): From 86a9ccefcbe3cdd0a621c504992b907fe05266e1 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:05:25 -0400 Subject: [PATCH 0590/1650] argspec replaced with namedtuple FuncProps, in_arg removed from original argspec and made into class var --- bpython/repl.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 6a9c0ebfc..40a5a4d15 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -359,7 +359,8 @@ def __init__(self, interp, config): self.history = [] self.evaluating = False self.matches_iter = MatchesIterator() - self.argspec = None + self.funcprops = None + self.arg_pos = None self.current_func = None self.highlighted_paren = None self._C = {} @@ -468,8 +469,8 @@ def get_object(self, name): def get_args(self): """Check if an unclosed parenthesis exists, then attempt to get the - argspec() for it. On success, update self.argspec and return True, - otherwise set self.argspec to None and return False""" + argspec() for it. On success, update self.funcprops,self.arg_pos and return True, + otherwise set self.funcprops to None and return False""" self.current_func = None @@ -532,11 +533,11 @@ def get_args(self): except AttributeError: return None self.current_func = f - - self.argspec = inspection.getargspec(func, f) - if self.argspec: - self.argspec.append(arg_number) + self.funcprops = inspection.getfuncprops(func, f) + if self.funcprops: + self.arg_pos = arg_number return True + self.arg_pos = None return False def get_source_of_current_name(self): @@ -567,7 +568,7 @@ def get_source_of_current_name(self): def set_docstring(self): self.docstring = None if not self.get_args(): - self.argspec = None + self.funcprops = None elif self.current_func is not None: try: self.docstring = pydoc.getdoc(self.current_func) @@ -609,7 +610,7 @@ def complete(self, tab=False): cursor_offset=self.cursor_offset, line=self.current_line, locals_=self.interp.locals, - argspec=self.argspec, + argspec=self.funcprops, current_block='\n'.join(self.buffer + [self.current_line]), complete_magic_methods=self.config.complete_magic_methods, history=self.history) @@ -618,7 +619,7 @@ def complete(self, tab=False): if len(matches) == 0: self.matches_iter.clear() - return bool(self.argspec) + return bool(self.funcprops) self.matches_iter.update(self.cursor_offset, self.current_line, matches, completer) From 0ace2c47e4cf6cdaf185e07cc00b190296ecf2f0 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:07:28 -0400 Subject: [PATCH 0591/1650] Replaced argspec list with namedtuple FuncProps for better readability --- bpython/cli.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 24a2e97bb..6b26d1343 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -699,16 +699,17 @@ def mkargspec(self, topline, down): sturdy.""" r = 3 - fn = topline[0] - args = topline[1][0] - kwargs = topline[1][3] - _args = topline[1][1] - _kwargs = topline[1][2] - is_bound_method = topline[2] - in_arg = topline[3] + fn = topline.func + args = topline.arginfo.args + kwargs = topline.arginfo.defaults + _args = topline.arginfo.varargs + _kwargs = topline.arginfo.varkwargs + is_bound_method = topline.is_bound_method + in_arg = topline.in_arg + print "\n\nprinting topline",topline if py3: - kwonly = topline[1][4] - kwonly_defaults = topline[1][5] or dict() + kwonly = topline.arginfo.kwonly + kwonly_defaults = topline.kwonly_defaults or dict() max_w = int(self.scr.getmaxyx()[1] * 0.6) self.list_win.erase() self.list_win.resize(3, max_w) @@ -1454,7 +1455,7 @@ def tab(self, back=False): current_match = back and self.matches_iter.previous() \ or next(self.matches_iter) try: - self.show_list(self.matches_iter.matches, topline=self.argspec, + self.show_list(self.matches_iter.matches, topline=self.funcprops, formatter=self.matches_iter.completer.format, current_item=current_match) except curses.error: From bd7e8e4848eff46a10054daefecc7a401a0ce5c4 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:11:14 -0400 Subject: [PATCH 0592/1650] Replacing argspec with namedtuples FuncProps, changed func signature to include arg_pos --- bpython/curtsiesfrontend/replpainter.py | 36 ++++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 2dcbeaef0..3e10816d2 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -72,19 +72,17 @@ def matches_lines(rows, columns, matches, current, config, format): return matches_lines -def formatted_argspec(argspec, columns, config): +def formatted_argspec(funcprops, arg_pos, columns, config): # Pretty directly taken from bpython.cli - is_bound_method = argspec[2] - func = argspec[0] - args = argspec[1][0] - kwargs = argspec[1][3] - _args = argspec[1][1] # *args - _kwargs = argspec[1][2] # **kwargs - is_bound_method = argspec[2] - in_arg = argspec[3] + func = funcprops.func + args = funcprops.argspec.args + kwargs = funcprops.argspec.defaults + _args = funcprops.argspec.varargs + _kwargs = funcprops.argspec.varkwargs + is_bound_method = funcprops.is_bound_method if py3: - kwonly = argspec[1][4] - kwonly_defaults = argspec[1][5] or dict() + kwonly = funcprops.argspec.kwonly + kwonly_defaults = funcprops.argspec.kwonly_defaults or dict() arg_color = func_for_letter(config.color_scheme['name']) func_color = func_for_letter(config.color_scheme['name'].swapcase()) @@ -95,16 +93,16 @@ def formatted_argspec(argspec, columns, config): s = func_color(func) + arg_color(': (') - if is_bound_method and isinstance(in_arg, int): + if is_bound_method and isinstance(arg_pos, int): # TODO what values could this have? - in_arg += 1 + arg_pos += 1 for i, arg in enumerate(args): kw = None if kwargs and i >= len(args) - len(kwargs): kw = str(kwargs[i - (len(args) - len(kwargs))]) - color = token_color if in_arg in (i, arg) else arg_color - if i == in_arg or arg == in_arg: + color = token_color if arg_pos in (i, arg) else arg_color + if i == arg_pos or arg == arg_pos: color = bolds[color] if not py3: @@ -135,7 +133,7 @@ def formatted_argspec(argspec, columns, config): for arg in kwonly: s += punctuation_color(', ') color = token_color - if in_arg: + if arg_pos: color = bolds[color] s += color(arg) default = kwonly_defaults.get(arg, marker) @@ -159,13 +157,13 @@ def formatted_docstring(docstring, columns, config): for line in docstring.split('\n')), []) -def paint_infobox(rows, columns, matches, argspec, match, docstring, config, +def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, config, format): - """Returns painted completions, argspec, match, docstring etc.""" + """Returns painted completions, funcprops, match, docstring etc.""" if not (rows and columns): return fsarray(0, 0) width = columns - 4 - lines = ((formatted_argspec(argspec, width, config) if argspec else []) + + lines = ((formatted_argspec(funcprops, arg_pos, width, config) if funcprops else []) + (matches_lines(rows, width, matches, match, config, format) if matches else []) + (formatted_docstring(docstring, width, config) From d8fdc2fad4209e0220903860f232cd4898e1911e Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:16:03 -0400 Subject: [PATCH 0593/1650] Replaced argspec with namedtuple FuncProps --- bpython/curtsiesfrontend/repl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index b460b70f5..d66d3fe06 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -909,7 +909,7 @@ def add_to_incremental_search(self, char=None, backspace=False): def update_completion(self, tab=False): """Update visible docstring and matches, and possibly hide/show completion box""" - # Update autocomplete info; self.matches_iter and self.argspec + # Update autocomplete info; self.matches_iter and self.funcprops # Should be called whenever the completion box might need to appear / dissapear # when current line or cursor offset changes, unless via selecting a match self.current_match = None @@ -1272,7 +1272,8 @@ def move_screen_up(current_line_start_row): infobox = paint.paint_infobox(info_max_rows, int(width * self.config.cli_suggestion_width), self.matches_iter.matches, - self.argspec, + self.funcprops, + self.arg_pos, self.current_match, self.docstring, self.config, From 4d6861cd005d85ad6725dccd0acf8ad82c0cbb9c Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:23:09 -0400 Subject: [PATCH 0594/1650] Refactored test file for argspec changing into named tuple funcprop --- bpython/test/test_curtsies_painting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 157972108..005431128 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -109,8 +109,8 @@ def test_argspec(self): def foo(x, y, z=10): "docstring!" pass - argspec = inspection.getargspec('foo', foo) + [1] - array = replpainter.formatted_argspec(argspec, 30, setup_config()) + argspec = inspection.getfuncprops('foo', foo) + array = replpainter.formatted_argspec(argspec, 1, 30, setup_config()) screen = [bold(cyan('foo')) + cyan(':') + cyan(' ') + cyan('(') + cyan('x') + yellow(',') + yellow(' ') + bold(cyan('y')) + yellow(',') + yellow(' ') + cyan('z') + yellow('=') + From 3057bbed1c95aefad2d9cc683680f9744cad14ad Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:24:13 -0400 Subject: [PATCH 0595/1650] Refactored test file for argspec changing into named tuple funcprop --- bpython/test/test_inspection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index b91f0ffbb..cf5a1aad5 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -62,22 +62,22 @@ def fails(spam=['-a', '-b']): self.assertEqual(str(['-a', '-b']), default_arg_repr, 'This test is broken (repr does not match), fix me.') - argspec = inspection.getargspec('fails', fails) - defaults = argspec[1][3] + argspec = inspection.getfuncprops('fails', fails) + defaults = argspec.argspec.defaults self.assertEqual(str(defaults[0]), default_arg_repr) def test_pasekeywordpairs_string(self): def spam(eggs="foo, bar"): pass - defaults = inspection.getargspec("spam", spam)[1][3] + defaults = inspection.getfuncprops("spam", spam).argspec.defaults self.assertEqual(repr(defaults[0]), "'foo, bar'") def test_parsekeywordpairs_multiple_keywords(self): def spam(eggs=23, foobar="yay"): pass - defaults = inspection.getargspec("spam", spam)[1][3] + defaults = inspection.getfuncprops("spam", spam).argspec.defaults self.assertEqual(repr(defaults[0]), "23") self.assertEqual(repr(defaults[1]), "'yay'") From 83abeb60f0406a8b0674fa1f12f155e07b502fe5 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 14:24:43 -0400 Subject: [PATCH 0596/1650] Refactored test file for argspec changing into named tuple funcprop --- bpython/test/test_repl.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 57d83b147..8f49d17c0 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -171,22 +171,22 @@ def test_syntax_error_parens(self): def test_kw_arg_position(self): self.set_input_line("spam(a=0") self.assertTrue(self.repl.get_args()) - self.assertEqual(self.repl.argspec[3], "a") + self.assertEqual(self.repl.arg_pos, "a") self.set_input_line("spam(1, b=1") self.assertTrue(self.repl.get_args()) - self.assertEqual(self.repl.argspec[3], "b") + self.assertEqual(self.repl.arg_pos, "b") self.set_input_line("spam(1, c=2") self.assertTrue(self.repl.get_args()) - self.assertEqual(self.repl.argspec[3], "c") + self.assertEqual(self.repl.arg_pos, "c") def test_lambda_position(self): self.set_input_line("spam(lambda a, b: 1, ") self.assertTrue(self.repl.get_args()) - self.assertTrue(self.repl.argspec) + self.assertTrue(self.repl.funcprops) # Argument position - self.assertEqual(self.repl.argspec[3], 1) + self.assertEqual(self.repl.arg_pos, 1) def test_issue127(self): self.set_input_line("x=range(") @@ -428,7 +428,7 @@ def test_simple_tab_complete(self): self.repl.print_line = mock.Mock() self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() - self.repl.argspec = mock.Mock() + self.repl.funcprops = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "foobar") self.repl.s = "foo" @@ -471,7 +471,7 @@ def test_back_parameter(self): self.repl.matches_iter.previous.return_value = "previtem" self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() - self.repl.argspec = mock.Mock() + self.repl.funcprops = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "previtem") self.repl.print_line = mock.Mock() self.repl.s = "foo" From 6586815f155bf283963af9aa154de2b43991c603 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 15:55:31 -0400 Subject: [PATCH 0597/1650] Argspec - Named tuple FuncProps related refactoring --- bpython/cli.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 6b26d1343..90462f990 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -461,7 +461,9 @@ def complete(self, tab=False): list_win_visible = repl.Repl.complete(self, tab) if list_win_visible: try: - self.show_list(self.matches_iter.matches, topline=self.argspec, formatter=self.matches_iter.completer.format) + self.show_list(self.matches_iter.matches, self.arg_pos, + topline=self.funcprops, + formatter=self.matches_iter.completer.format) except curses.error: # XXX: This is a massive hack, it will go away when I get # cusswords into a good enough state that we can start @@ -691,7 +693,7 @@ def lf(self): self.print_line(self.s, newline=True) self.echo("\n") - def mkargspec(self, topline, down): + def mkargspec(self, topline, in_arg, down): """This figures out what to do with the argspec and puts it nicely into the list window. It returns the number of lines used to display the argspec. It's also kind of messy due to it having to call so many @@ -700,15 +702,13 @@ def mkargspec(self, topline, down): r = 3 fn = topline.func - args = topline.arginfo.args - kwargs = topline.arginfo.defaults - _args = topline.arginfo.varargs - _kwargs = topline.arginfo.varkwargs + args = topline.argspec.args + kwargs = topline.argspec.defaults + _args = topline.argspec.varargs + _kwargs = topline.argspec.varkwargs is_bound_method = topline.is_bound_method - in_arg = topline.in_arg - print "\n\nprinting topline",topline if py3: - kwonly = topline.arginfo.kwonly + kwonly = topline.argspec.kwonly kwonly_defaults = topline.kwonly_defaults or dict() max_w = int(self.scr.getmaxyx()[1] * 0.6) self.list_win.erase() @@ -1254,7 +1254,7 @@ def write(self, s): self.s_hist.append(s.rstrip()) - def show_list(self, items, topline=None, formatter=None, current_item=None): + def show_list(self, items, arg_pos, topline=None, formatter=None, current_item=None): shared = Struct() shared.cols = 0 @@ -1276,7 +1276,7 @@ def show_list(self, items, topline=None, formatter=None, current_item=None): current_item = formatter(current_item) if topline: - height_offset = self.mkargspec(topline, down) + 1 + height_offset = self.mkargspec(topline, arg_pos, down) + 1 else: height_offset = 0 @@ -1455,7 +1455,8 @@ def tab(self, back=False): current_match = back and self.matches_iter.previous() \ or next(self.matches_iter) try: - self.show_list(self.matches_iter.matches, topline=self.funcprops, + self.show_list(self.matches_iter.matches, self.arg_pos, + topline=self.funcprops, formatter=self.matches_iter.completer.format, current_item=current_match) except curses.error: From de6e387246fb22a272eb9e2711f6146e40fbd785 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 15:57:31 -0400 Subject: [PATCH 0598/1650] Refactored argspec with replacement named tuple FuncProps --- bpython/urwid.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index f10ceb60b..d2c392d72 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -745,13 +745,14 @@ def _populate_completion(self): widget_list.pop() # This is just me flailing around wildly. TODO: actually write. if self.complete(): - if self.argspec: + if self.funcprops: # This is mostly just stolen from the cli module. - func_name, args, is_bound, in_arg = self.argspec + func_name, args, is_bound = self.funcprops + in_arg = self.arg_pos args, varargs, varkw, defaults = args[:4] if py3: - kwonly = self.argspec[1][4] - kwonly_defaults = self.argspec[1][5] or {} + kwonly = self.funcprops.argspec.kwonly + kwonly_defaults = self.funcprops.argspec.kwonly_defaults or {} else: kwonly, kwonly_defaults = [], {} markup = [('bold name', func_name), From e72586a15df2a90ffc297b13f675c742e85d98e2 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 15:58:11 -0400 Subject: [PATCH 0599/1650] Refactored test file for argspec to FuncProp changes --- bpython/test/test_repl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 8f49d17c0..2bb13f4b8 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -429,6 +429,7 @@ def test_simple_tab_complete(self): self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() self.repl.funcprops = mock.Mock() + self.repl.arg_pos = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "foobar") self.repl.s = "foo" @@ -472,6 +473,7 @@ def test_back_parameter(self): self.repl.matches_iter.is_cseq.return_value = False self.repl.show_list = mock.Mock() self.repl.funcprops = mock.Mock() + self.repl.arg_pos = mock.Mock() self.repl.matches_iter.cur_line.return_value = (None, "previtem") self.repl.print_line = mock.Mock() self.repl.s = "foo" From 27b788cb4e54fdaa703d485ab7dcda565224240a Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 16:27:19 -0400 Subject: [PATCH 0600/1650] Added accidentally deleted Copyright --- bpython/autocomplete.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a172ca4c0..0fd64a5e6 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -2,6 +2,7 @@ # The MIT License # +# Copyright (c) 2009-2011 the bpython authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal From 85a2a5d02373e43f3ca958ba5b330d1109a0b689 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 16:31:09 -0400 Subject: [PATCH 0601/1650] Changed case for Argspec named tuple definition to ArgSpec --- bpython/inspection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 2e3958e87..258f18003 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -41,7 +41,7 @@ _name = LazyReCompile(r'[a-zA-Z_]\w*$') -Argspec = namedtuple('Argspec', ['args', 'varargs', 'varkwargs', 'defaults', +ArgSpec = namedtuple('ArgSpec', ['args', 'varargs', 'varkwargs', 'defaults', 'kwonly', 'kwonly_defaults', 'annotations']) FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method']) @@ -191,7 +191,7 @@ def getpydocspec(f, func): if not hasattr(f, '__name__') or s.groups()[0] != f.__name__: return None - argspec = Argspec(list(), None, None, list(), list(), dict(), None) + argspec = ArgSpec(list(), None, None, list(), list(), dict(), None) for arg in s.group(2).split(','): arg = arg.strip() @@ -241,7 +241,7 @@ def getfuncprops(func, f): fixlongargs(f, argspec) if len(argspec) == 4: argspec = argspec + [list(),dict(),None] - argspec = Argspec(*argspec) + argspec = ArgSpec(*argspec) fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError): with AttrCleaner(f): From 2141cf78bcd58ef4978935494a1fc904e7a57380 Mon Sep 17 00:00:00 2001 From: noella Date: Mon, 8 Jun 2015 16:35:45 -0400 Subject: [PATCH 0602/1650] Updated copyright year --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 0fd64a5e6..fcf011434 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -2,7 +2,7 @@ # The MIT License # -# Copyright (c) 2009-2011 the bpython authors. +# Copyright (c) 2009-2015 the bpython authors. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal From 6dfbc4de87fd1f7c9d10f4457515f0b07b42d79b Mon Sep 17 00:00:00 2001 From: noella Date: Tue, 9 Jun 2015 13:39:49 -0400 Subject: [PATCH 0603/1650] Fixed a named tuple being modifying causing exceptions --- bpython/inspection.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 258f18003..7a700a700 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -181,36 +181,41 @@ def fixlongargs(f, argspec): def getpydocspec(f, func): try: - docstring = pydoc.getdoc(f) + argspec = pydoc.getdoc(f) except NameError: return None - s = getpydocspec_re.search(docstring) + + s = getpydocspec_re.search(argspec) if s is None: return None if not hasattr(f, '__name__') or s.groups()[0] != f.__name__: return None - argspec = ArgSpec(list(), None, None, list(), list(), dict(), None) - + args = list() + defaults = list() + varargs = varkwargs = None + kwonly_args = list() + kwonly_defaults = dict() for arg in s.group(2).split(','): arg = arg.strip() if arg.startswith('**'): - argspec.varkwargs = arg[2:] + varkwargs = arg[2:] elif arg.startswith('*'): - argspec.varargs = arg[1:] + varargs = arg[1:] else: arg, _, default = arg.partition('=') - if argspec.varargs is not None: - argspec.kwonly_args.append(arg) + if varargs is not None: + kwonly_args.append(arg) if default: - argspec.kwonly_defaults[arg] = default + kwonly_defaults[arg] = default else: - argspec.args.append(arg) + args.append(arg) if default: - argspec.defaults.append(default) + defaults.append(default) - return argspec + return ArgSpec(args, varargs, varkwargs, default, kwonly_args, + kwonly_defaults, None) def getfuncprops(func, f): From d172921c95fd8dbed297ba2f9e3a12a4d32f7a8d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 9 Jun 2015 22:43:01 +0200 Subject: [PATCH 0604/1650] Make pep8 happy Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/manual_readline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 11a4efa02..02c7cbf03 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -295,6 +295,7 @@ def transpose_word_before_cursor(cursor_offset, line): # bonus functions (not part of readline) + @edit_keys.on('') def uppercase_next_word(cursor_offset, line): return cursor_offset, line # TODO Not implemented From a7d729e3cbce651f5a8b8be38adf8ae6e9f63c2f Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 00:56:58 -0400 Subject: [PATCH 0605/1650] PEP8 --- bpython/inspection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 258f18003..511ef6895 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -42,7 +42,7 @@ _name = LazyReCompile(r'[a-zA-Z_]\w*$') ArgSpec = namedtuple('ArgSpec', ['args', 'varargs', 'varkwargs', 'defaults', - 'kwonly', 'kwonly_defaults', 'annotations']) + 'kwonly', 'kwonly_defaults', 'annotations']) FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method']) @@ -240,7 +240,7 @@ def getfuncprops(func, f): argspec = list(argspec) fixlongargs(f, argspec) if len(argspec) == 4: - argspec = argspec + [list(),dict(),None] + argspec = argspec + [list(), dict(), None] argspec = ArgSpec(*argspec) fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError): From 1424a229d882620748bc1c236b1d5109cdc905ef Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 00:58:08 -0400 Subject: [PATCH 0606/1650] PEP8 --- bpython/autocomplete.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index fcf011434..b2fbc3880 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -127,9 +127,10 @@ def format(self, word): def substitute(self, cursor_offset, line, match): """Returns a cursor offset and line with match swapped in""" - lpart = self.locate(cursor_offset, line) - result = lpart.start + len(match), line[:lpart.start] + match + line[lpart.end:] - return result + lpart = self.locate(cursor_offset, line) + offset = lpart.start + len(match) + changed_line = line[:lpart.start] + match + line[lpart.end:] + return offset, changed_line @property def shown_before_tab(self): @@ -401,7 +402,8 @@ def matches(self, cursor_offset, line, **kwargs): # if identifier isn't ascii, don't complete (syntax error) if word is None: continue - if self.method_match(word, n, r.word) and word != "__builtins__": + if (self.method_match(word, n, r.word) and + word != "__builtins__"): matches.add(_callable_postfix(val, word)) return matches From b89969642ccc876c23506f807d005792c4dd80dc Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 00:59:37 -0400 Subject: [PATCH 0607/1650] PEP8 --- bpython/curtsiesfrontend/replpainter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 3e10816d2..94e813ecf 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -157,13 +157,14 @@ def formatted_docstring(docstring, columns, config): for line in docstring.split('\n')), []) -def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, config, - format): +def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, + config, format): """Returns painted completions, funcprops, match, docstring etc.""" if not (rows and columns): return fsarray(0, 0) width = columns - 4 - lines = ((formatted_argspec(funcprops, arg_pos, width, config) if funcprops else []) + + lines = ((formatted_argspec(funcprops, arg_pos, width, config) + if funcprops else []) + (matches_lines(rows, width, matches, match, config, format) if matches else []) + (formatted_docstring(docstring, width, config) From 191e43486bd6f27743c45716cb81ff11227d3e1a Mon Sep 17 00:00:00 2001 From: noella Date: Wed, 10 Jun 2015 01:00:18 -0400 Subject: [PATCH 0608/1650] PEP8 --- bpython/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 40a5a4d15..433d2732a 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -469,8 +469,8 @@ def get_object(self, name): def get_args(self): """Check if an unclosed parenthesis exists, then attempt to get the - argspec() for it. On success, update self.funcprops,self.arg_pos and return True, - otherwise set self.funcprops to None and return False""" + argspec() for it. On success, update self.funcprops,self.arg_pos and + return True, otherwise set self.funcprops to None and return False""" self.current_func = None From 72be2cd09c45f19a6b866d6d838d2805f8ba9cf6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 21 Jun 2015 14:56:43 +0200 Subject: [PATCH 0609/1650] Failing test for #544 --- bpython/test/test_autocomplete.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index bfe1b0b66..8ec494a09 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -20,7 +20,8 @@ from bpython._py3compat import py3 from bpython.test import mock -if sys.version_info[:2] >= (3, 4): +is_py34 = sys.version_info[:2] >= (3, 4) +if is_py34: glob_function = 'glob.iglob' else: glob_function = 'glob.glob' @@ -308,6 +309,13 @@ def test_completions_starting_with_different_cases(self): [Comp('Abc', 'bc'), Comp('ade', 'de')]) self.assertSetEqual(matches, set(['ade'])) + @unittest.skipUnless(is_py34, 'asyncio required') + def test_issue_544(self): + com = autocomplete.MultilineJediCompletion() + code = '@asyncio.coroutine\ndef' + history = ('import asyncio', '@asyncio.coroutin') + com.matches(3, 'def', current_block=code, history=history) + class TestGlobalCompletion(unittest.TestCase): From 04ca930a703ab70fae18ba08c810a84ba1fb5cab Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 21 Jun 2015 14:57:34 +0200 Subject: [PATCH 0610/1650] Work around jedi bugs (fixes #544) This might break jedi based completion. Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index b2fbc3880..754b3a394 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -479,11 +479,9 @@ def matches(self, cursor_offset, line, **kwargs): script = jedi.Script(history, len(history.splitlines()), cursor_offset, 'fake.py') completions = script.completions() - except jedi.NotFoundError: - self._orig_start = None - return None - except IndexError: - # for #483 + except (jedi.NotFoundError, IndexError, KeyError): + # IndexError for #483 + # KeyError for #544 self._orig_start = None return None From e1b0dcd94f005a700431dd217ad617d7b6208692 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 24 Jun 2015 22:00:44 +0200 Subject: [PATCH 0611/1650] Do not print version and help with -q (#fixes #527) Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 88dc55009..693fde8a3 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -75,7 +75,8 @@ def main(args=None, locals_=None, banner=None): # expected for interactive sessions (vanilla python does it) sys.path.insert(0, '') - print(bpargs.version_banner()) + if not options.quiet: + print(bpargs.version_banner()) try: exit_value = mainloop(config, locals_, banner, interp, paste, interactive=(not exec_args)) From ff4ad26ca93cbde3472c29a73b7c2b56b56a11f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 24 Jun 2015 22:08:29 +0200 Subject: [PATCH 0612/1650] Allow multi-line banners (fixes #538) Also distinguish between banners and welcome message. Signed-off-by: Sebastian Ramacher --- bpython/curtsies.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 693fde8a3..91843120e 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -28,7 +28,11 @@ # WARNING Will be a problem if more than one repl is ever instantiated this way -def main(args=None, locals_=None, banner=None): +def main(args=None, locals_=None, banner=None, welcome_message=None): + """ + banner is displayed directly after the version information. + welcome_message is passed on to Repl and displayed in the statusbar. + """ translations.init() config, options, exec_args = bpargs.parse(args, ( @@ -77,8 +81,10 @@ def main(args=None, locals_=None, banner=None): if not options.quiet: print(bpargs.version_banner()) + if banner is not None: + print(banner) try: - exit_value = mainloop(config, locals_, banner, interp, paste, + exit_value = mainloop(config, locals_, welcome_message, interp, paste, interactive=(not exec_args)) except (SystemExitFromCodeGreenlet, SystemExit) as e: exit_value = e.args From b71f41138732d862a72342b34407092a3e4eb162 Mon Sep 17 00:00:00 2001 From: sharow Date: Mon, 6 Jul 2015 06:37:28 +0900 Subject: [PATCH 0613/1650] fix: Reload and Auto-reloading doesn't work when using Python3.x --- bpython/curtsiesfrontend/repl.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d66d3fe06..a5f5dfa03 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -450,7 +450,7 @@ def smarter_request_reload(files_modified=()): self.incremental_search_target = '' - self.original_modules = sys.modules.keys() + self.original_modules = set(sys.modules.keys()) self.width = None self.height = None @@ -846,9 +846,8 @@ def clear_modules_and_reevaluate(self): if self.watcher: self.watcher.reset() cursor, line = self.cursor_offset, self.current_line - for modname in sys.modules.keys(): - if modname not in self.original_modules: - del sys.modules[modname] + for modname in (set(sys.modules.keys()) - self.original_modules): + del sys.modules[modname] self.reevaluate(insert_into_history=True) self.cursor_offset, self.current_line = cursor, line self.status_bar.message(_('Reloaded at %s by user.') % From 5eed6afc19c4a57026ef2d230df4573d0638bf8e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 7 Jul 2015 20:01:40 +0200 Subject: [PATCH 0614/1650] Fix cli after named-tuple refactoring Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index 90462f990..b04a0636e 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -709,7 +709,7 @@ def mkargspec(self, topline, in_arg, down): is_bound_method = topline.is_bound_method if py3: kwonly = topline.argspec.kwonly - kwonly_defaults = topline.kwonly_defaults or dict() + kwonly_defaults = topline.argspec.kwonly_defaults or dict() max_w = int(self.scr.getmaxyx()[1] * 0.6) self.list_win.erase() self.list_win.resize(3, max_w) From 2c0958fb689662081d24bcdc2e951f51f7174f23 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 7 Jul 2015 20:03:51 +0200 Subject: [PATCH 0615/1650] Fix Python 3 compat (fixes #550) Signed-off-by: Sebastian Ramacher --- bpython/pager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/pager.py b/bpython/pager.py index 20b1af743..70448672f 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -29,6 +29,8 @@ import sys import shlex +from bpython._py3compat import py3 + def get_pager_command(default='less -rf'): command = shlex.split(os.environ.get('PAGER', default)) @@ -51,7 +53,7 @@ def page(data, use_internal=False): curses.endwin() try: popen = subprocess.Popen(command, stdin=subprocess.PIPE) - if isinstance(data, unicode): + if py3 or isinstance(data, unicode): data = data.encode(sys.__stdout__.encoding, 'replace') popen.stdin.write(data) popen.stdin.close() From dc0f2945b2176d4741a8b8688105972809c4c7e7 Mon Sep 17 00:00:00 2001 From: sharow Date: Sat, 11 Jul 2015 17:35:32 +0900 Subject: [PATCH 0616/1650] add test --- bpython/test/test_curtsies_repl.py | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 5f85825e0..1e405bffc 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -6,6 +6,7 @@ import sys import tempfile import io +from functools import partial from contextlib import contextmanager from six.moves import StringIO @@ -284,6 +285,68 @@ def test_variable_is_cleared(self): self.assertNotIn('b', self.repl.interp.locals) +class TestCurtsiesReevaluateWithImport(TestCase): + def setUp(self): + self.repl = create_repl() + self.open = partial(io.open, mode='wt', encoding='utf-8') + self.dont_write_bytecode = sys.dont_write_bytecode + sys.dont_write_bytecode = True + + def tearDown(self): + sys.dont_write_bytecode = self.dont_write_bytecode + + def push(self, line): + self.repl._current_line = line + self.repl.on_enter() + + def head(self, path): + self.push('import sys') + self.push('sys.path.append("%s")' % (path)) + + @staticmethod + @contextmanager + def tempfile(): + with tempfile.NamedTemporaryFile(suffix='.py') as temp: + path, name = os.path.split(temp.name) + yield temp.name, path, name.replace('.py', '') + + def test_module_content_changed(self): + with self.tempfile() as (fullpath, path, modname): + with self.open(fullpath) as f: + f.write('a = 0\n') + self.head(path) + self.push('import %s' % (modname)) + self.push('a = %s.a' % (modname)) + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 0) + with self.open(fullpath) as f: + f.write('a = 1\n') + self.repl.clear_modules_and_reevaluate() + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 1) + + def test_import_module_with_rewind(self): + with self.tempfile() as (fullpath, path, modname): + with self.open(fullpath) as f: + f.write('a = 0\n') + self.head(path) + self.push('import %s' % (modname)) + self.assertIn(modname, self.repl.interp.locals) + self.repl.undo() + self.assertNotIn(modname, self.repl.interp.locals) + self.repl.clear_modules_and_reevaluate() + self.assertNotIn(modname, self.repl.interp.locals) + self.push('import %s' % (modname)) + self.push('a = %s.a' % (modname)) + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 0) + with self.open(fullpath) as f: + f.write('a = 1\n') + self.repl.clear_modules_and_reevaluate() + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 1) + + class TestCurtsiesPagerText(TestCase): def setUp(self): From 51ffb81c2c7b49dfaf1e126cfb5836d4e5042b44 Mon Sep 17 00:00:00 2001 From: Pete Anderson Date: Tue, 14 Jul 2015 04:43:37 -0400 Subject: [PATCH 0617/1650] Keep autocomplete errors from crashing bpython Perhaps a popup of some sort informing the user that an error has occurred would be better than just swallowing the error as I've done here, but I feel like a misbehaving completer should crash the application. The completer that prompted this for me is FilenameCompletion. I've got a test file in my directory created with `touch $'with\xFFhigh ascii'. If I type an open quote and a w in bpython, it crashes. It's because From python, if I do: >>> import glob >>> glob.glob(u'w*') # this is what FileCompletion will end up calling [u'without high ascii', u'with\uf0ffhigh ascii'] >>> But if I do it from bpython: >>> import glob >>> glob.glob(u'w*'0 [u'without high ascii', 'with\xffhigh ascii'] >>> For some reason, glob is returning one unicode and one str. Then when get_completer calls sorted(matches), sorted throws up when it tries to decode the str from ASCII. I don't know why glob is behaving this way or what the fix is, but I do know that it's not worth crashing bpython whenever I type 'w --- bpython/autocomplete.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 754b3a394..5ee4f2fa8 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -544,10 +544,14 @@ def get_completer(completers, cursor_offset, line, **kwargs): double underscore methods like __len__ in method signatures """ - for completer in completers: - matches = completer.matches(cursor_offset, line, **kwargs) - if matches is not None: - return sorted(matches), (completer if matches else None) + try: + for completer in completers: + matches = completer.matches(cursor_offset, line, **kwargs) + if matches is not None: + return sorted(matches), (completer if matches else None) + except: + pass + return [], None From d9aec6769076d521cb186d71f513338415c8d398 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 2 Aug 2015 21:12:46 +0200 Subject: [PATCH 0618/1650] Fix attrib decorator (fixes #553) Signed-off-by: Sebastian Ramacher --- bpython/test/test_args.py | 6 ++++-- bpython/test/test_crashers.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 62b49aab5..ef53cfda9 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -14,8 +14,10 @@ try: from nose.plugins.attrib import attr except ImportError: - def attr(func, *args, **kwargs): - return func + def attr(*args, **kwargs): + def identity(func): + return func + return identity @attr(speed='slow') diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 5db661bbf..1aea547e7 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -27,8 +27,10 @@ class TrialTestCase(object): try: from nose.plugins.attrib import attr except ImportError: - def attr(func, *args, **kwargs): - return func + def attr(*args, **kwargs): + def identity(func): + return func + return identity TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") From a17999d57f5b8e9872dde39894c6764a839bd275 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 18 Aug 2015 21:03:07 +0200 Subject: [PATCH 0619/1650] Fix a typo Signed-off-by: Sebastian Ramacher --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 14aec69f2..0dfb4feff 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -214,7 +214,7 @@ def getpydocspec(f, func): if default: defaults.append(default) - return ArgSpec(args, varargs, varkwargs, default, kwonly_args, + return ArgSpec(args, varargs, varkwargs, defaults, kwonly_args, kwonly_defaults, None) From c08b42d6dcc31f136e556d9ca2c7549f638ea1bd Mon Sep 17 00:00:00 2001 From: Weston Vial Date: Wed, 26 Aug 2015 15:39:12 -0400 Subject: [PATCH 0620/1650] Fix bug #548 - Transpose when empty line crashes --- bpython/curtsiesfrontend/manual_readline.py | 6 ++++-- bpython/test/test_manual_readline.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 02c7cbf03..24a1b9a27 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -280,11 +280,13 @@ def yank_prev_killed_text(cursor_offset, line, cut_buffer): @edit_keys.on(config='transpose_chars_key') def transpose_character_before_cursor(cursor_offset, line): + if cursor_offset == 0: + return cursor_offset, line return (min(len(line), cursor_offset + 1), - line[:cursor_offset-1] + + line[:cursor_offset - 1] + (line[cursor_offset] if len(line) > cursor_offset else '') + line[cursor_offset - 1] + - line[cursor_offset+1:]) + line[cursor_offset + 1:]) @edit_keys.on('') diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 6ef610638..f1e24b780 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -201,6 +201,16 @@ def test_transpose_character_before_cursor(self): "adf s|asdf", "adf as|sdf"], transpose_character_before_cursor) + def test_transpose_empty_line(self): + self.assertEquals(transpose_character_before_cursor(0, ''), + (0,'')) + + def test_transpose_first_character(self): + self.assertEquals(transpose_character_before_cursor(0, 'a'), + transpose_character_before_cursor(0, 'a')) + self.assertEquals(transpose_character_before_cursor(0, 'as'), + transpose_character_before_cursor(0, 'as')) + def test_transpose_word_before_cursor(self): pass From 3abb483b4a109cbfe61d8ed6ca7e5eba3f9fa38f Mon Sep 17 00:00:00 2001 From: Weston Vial Date: Wed, 26 Aug 2015 16:14:14 -0400 Subject: [PATCH 0621/1650] Transpose characters if cursor is at the end of the line. Mimics emacs behavior. --- bpython/curtsiesfrontend/manual_readline.py | 4 +++- bpython/test/test_manual_readline.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 24a1b9a27..a919df14d 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -280,8 +280,10 @@ def yank_prev_killed_text(cursor_offset, line, cut_buffer): @edit_keys.on(config='transpose_chars_key') def transpose_character_before_cursor(cursor_offset, line): - if cursor_offset == 0: + if cursor_offset < 2: return cursor_offset, line + if cursor_offset == len(line): + return cursor_offset, line[:-2] + line[-1] + line[-2] return (min(len(line), cursor_offset + 1), line[:cursor_offset - 1] + (line[cursor_offset] if len(line) > cursor_offset else '') + diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index f1e24b780..3c25e3bb5 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -207,9 +207,15 @@ def test_transpose_empty_line(self): def test_transpose_first_character(self): self.assertEquals(transpose_character_before_cursor(0, 'a'), - transpose_character_before_cursor(0, 'a')) + (0, 'a')) self.assertEquals(transpose_character_before_cursor(0, 'as'), - transpose_character_before_cursor(0, 'as')) + (0, 'as')) + + def test_transpose_end_of_line(self): + self.assertEquals(transpose_character_before_cursor(1, 'a'), + (1, 'a')) + self.assertEquals(transpose_character_before_cursor(2, 'as'), + (2, 'sa')) def test_transpose_word_before_cursor(self): pass From 563f5cb8a9dfeada479d479433b9a5e78f8fea23 Mon Sep 17 00:00:00 2001 From: Jeppe Toustrup Date: Thu, 1 Oct 2015 22:53:38 +0200 Subject: [PATCH 0622/1650] Filter out two underscore attributes in auto completion This change will require you to write two underscores in order to get autocompletion of attributes starting with two underscores, as requested in #528. Fixes #528 --- bpython/autocomplete.py | 7 ++++++- bpython/test/test_autocomplete.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 754b3a394..9bb86d895 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -253,7 +253,12 @@ def matches(self, cursor_offset, line, **kwargs): # TODO add open paren for methods via _callable_prefix (or decide not # to) unless the first character is a _ filter out all attributes # starting with a _ - if not r.word.split('.')[-1].startswith('_'): + if r.word.split('.')[-1].startswith('__'): + pass + elif r.word.split('.')[-1].startswith('_'): + matches = set(match for match in matches + if not match.split('.')[-1].startswith('__')) + else: matches = set(match for match in matches if not match.split('.')[-1].startswith('_')) return matches diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 8ec494a09..3681485db 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -245,12 +245,12 @@ def test_att_matches_found_on_old_style_instance(self): locals_={'a': OldStyleFoo()}), set(['a.method', 'a.a', 'a.b'])) self.assertIn(u'a.__dict__', - self.com.matches(3, 'a._', locals_={'a': OldStyleFoo()})) + self.com.matches(3, 'a.__', locals_={'a': OldStyleFoo()})) @skip_old_style def test_att_matches_found_on_old_style_class_object(self): self.assertIn(u'A.__dict__', - self.com.matches(3, 'A._', locals_={'A': OldStyleFoo})) + self.com.matches(3, 'A.__', locals_={'A': OldStyleFoo})) @skip_old_style def test_issue536(self): @@ -260,7 +260,7 @@ def __getattr__(self, attr): locals_ = {'a': OldStyleWithBrokenGetAttr()} self.assertIn(u'a.__module__', - self.com.matches(3, 'a._', locals_=locals_)) + self.com.matches(3, 'a.__', locals_=locals_)) class TestMagicMethodCompletion(unittest.TestCase): From 9f3460b6c4d6619c9080faf4b3e2e6755c7f354b Mon Sep 17 00:00:00 2001 From: Jeppe Toustrup Date: Mon, 5 Oct 2015 22:12:00 +0200 Subject: [PATCH 0623/1650] Correct auto complete tests after double underscore change --- bpython/test/test_autocomplete.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 3681485db..24018f3d2 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -245,12 +245,12 @@ def test_att_matches_found_on_old_style_instance(self): locals_={'a': OldStyleFoo()}), set(['a.method', 'a.a', 'a.b'])) self.assertIn(u'a.__dict__', - self.com.matches(3, 'a.__', locals_={'a': OldStyleFoo()})) + self.com.matches(4, 'a.__', locals_={'a': OldStyleFoo()})) @skip_old_style def test_att_matches_found_on_old_style_class_object(self): self.assertIn(u'A.__dict__', - self.com.matches(3, 'A.__', locals_={'A': OldStyleFoo})) + self.com.matches(4, 'A.__', locals_={'A': OldStyleFoo})) @skip_old_style def test_issue536(self): @@ -260,7 +260,7 @@ def __getattr__(self, attr): locals_ = {'a': OldStyleWithBrokenGetAttr()} self.assertIn(u'a.__module__', - self.com.matches(3, 'a.__', locals_=locals_)) + self.com.matches(4, 'a.__', locals_=locals_)) class TestMagicMethodCompletion(unittest.TestCase): From 4df988b5b7f192eb4ceec8217c7290f6fefe0d90 Mon Sep 17 00:00:00 2001 From: Shibo Yao Date: Wed, 7 Oct 2015 07:31:34 -0500 Subject: [PATCH 0624/1650] Fix python 3 compatibility Python 3 doesn't allow addition of dict_items and list, but union works. --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index b04a0636e..3d68b68ca 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -281,7 +281,7 @@ def make_colors(config): } if platform.system() == 'Windows': - c = dict(c.items() + + c = dict(c.items() | [ ('K', 8), ('R', 9), From 668bf06f8b7ec8c239e43f359591257d7993e6c9 Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Wed, 25 Nov 2015 23:58:21 -0500 Subject: [PATCH 0625/1650] Show __new__ docstrings. Fixes #572 --- bpython/inspection.py | 4 +++- bpython/repl.py | 20 +++++++++++++++----- bpython/test/test_repl.py | 15 +++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 0dfb4feff..c4f05c086 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -231,7 +231,9 @@ def getfuncprops(func, f): try: is_bound_method = ((inspect.ismethod(f) and f.__self__ is not None) or (func_name == '__init__' and not - func.endswith('.__init__'))) + func.endswith('.__init__')) + or (func_name == '__new__' and not + func.endswith('.__new__'))) except: # if f is a method from a xmlrpclib.Server instance, func_name == # '__init__' throws xmlrpclib.Fault (see #202) diff --git a/bpython/repl.py b/bpython/repl.py index 433d2732a..dc13b2f6e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -527,11 +527,21 @@ def get_args(self): return False if inspect.isclass(f): - try: - if f.__init__ is not object.__init__: - f = f.__init__ - except AttributeError: - return None + class_f = None + + if (hasattr(f, '__init__') and + f.__init__ is not object.__init__): + class_f = f.__init__ + if ((not class_f or + not inspection.getfuncprops(func, class_f)) and + hasattr(f, '__new__') and + f.__new__ is not object.__new__ and + f.__new__.__class__ is not object.__new__.__class__): # py3 + class_f = f.__new__ + + if class_f: + f = class_f + self.current_func = f self.funcprops = inspection.getfuncprops(func, f) if self.funcprops: diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 2bb13f4b8..08ed7564c 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -138,6 +138,14 @@ def setUp(self): self.repl.push(" def spam(self, a, b, c):\n", False) self.repl.push(" pass\n", False) self.repl.push("\n", False) + self.repl.push("class SpammitySpam(object):\n", False) + self.repl.push(" def __init__(self, a, b, c):\n", False) + self.repl.push(" pass\n", False) + self.repl.push("\n", False) + self.repl.push("class WonderfulSpam(object):\n", False) + self.repl.push(" def __new__(self, a, b, c):\n", False) + self.repl.push(" pass\n", False) + self.repl.push("\n", False) self.repl.push("o = Spam()\n", False) self.repl.push("\n", False) @@ -207,6 +215,13 @@ def test_nonexistent_name(self): self.set_input_line("spamspamspam(") self.assertFalse(self.repl.get_args()) + def test_issue572(self): + self.set_input_line("SpammitySpam(") + self.assertTrue(self.repl.get_args()) + + self.set_input_line("WonderfulSpam(") + self.assertTrue(self.repl.get_args()) + class TestGetSource(unittest.TestCase): def setUp(self): From f16a248b5e933c22e03401d4f2ef11ab3dc0a3ec Mon Sep 17 00:00:00 2001 From: Shawn Axsom Date: Fri, 27 Nov 2015 02:08:28 +0000 Subject: [PATCH 0626/1650] Array item completion is working with strings. Pressing tab works for autocompletion of array items now --- bpython/autocomplete.py | 39 +++++++++++++++++++++++++++++++++++++++ bpython/line.py | 24 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 9bb86d895..1cca269f4 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -335,6 +335,44 @@ def list_attributes(self, obj): return dir(obj) +class ArrayObjectMembersCompletion(BaseCompletionType): + + def __init__(self, shown_before_tab=True, mode=SIMPLE): + self._shown_before_tab = shown_before_tab + self.completer = AttrCompletion(mode=mode) + + def matches(self, cursor_offset, line, **kwargs): + if 'locals_' not in kwargs: + return None + locals_ = kwargs['locals_'] + + r = self.locate(cursor_offset, line) + if r is None: + return None + member_part = r[2] + _, _, dexpr = lineparts.current_array_with_indexer(cursor_offset, line) + try: + locals_['temp_val_from_array'] = safe_eval(dexpr, locals_) + except (EvaluationError, IndexError): + return set() + + temp_line = line.replace(member_part, 'temp_val_from_array.') + + matches = self.completer.matches(len(temp_line), temp_line, **kwargs) + matches_with_correct_name = \ + set(match.replace('temp_val_from_array.', member_part) for match in matches) + + del locals_['temp_val_from_array'] + + return matches_with_correct_name + + def locate(self, current_offset, line): + return lineparts.current_array_item_member_name(current_offset, line) + + def format(self, match): + return after_last_dot(match) + + class DictKeyCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): @@ -565,6 +603,7 @@ def get_default_completer(mode=SIMPLE): MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), GlobalCompletion(mode=mode), + ArrayObjectMembersCompletion(mode=mode), CumulativeCompleter((AttrCompletion(mode=mode), ParameterNameCompletion(mode=mode)), mode=mode) diff --git a/bpython/line.py b/bpython/line.py index c1da3943b..52c7b2355 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -226,3 +226,27 @@ def current_string_literal_attr(cursor_offset, line): if m.start(4) <= cursor_offset and m.end(4) >= cursor_offset: return LinePart(m.start(4), m.end(4), m.group(4)) return None + + +current_array_with_indexer_re = LazyReCompile( + r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\])\.(.*)''') + + +def current_array_with_indexer(cursor_offset, line): + """an array and indexer, e.g. foo[1]""" + matches = current_array_with_indexer_re.finditer(line) + for m in matches: + if m.start(1) <= cursor_offset and m.end(1) <= cursor_offset: + return LinePart(m.start(1), m.end(1), m.group(1)) + + +current_array_item_member_name_re = LazyReCompile( + r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\]\.)(.*)''') + + +def current_array_item_member_name(cursor_offset, line): + """the member name after an array indexer, e.g. foo[1].bar""" + matches = current_array_item_member_name_re.finditer(line) + for m in matches: + if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: + return LinePart(m.start(1), m.end(2), m.group(1)) From b2867705927b7dbcd109f9382b5280e8da9a76fa Mon Sep 17 00:00:00 2001 From: Shawn Axsom Date: Fri, 27 Nov 2015 18:04:52 +0000 Subject: [PATCH 0627/1650] Able to handle nested arrays now when autocompleting for array items --- bpython/line.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 52c7b2355..15495d988 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -229,7 +229,7 @@ def current_string_literal_attr(cursor_offset, line): current_array_with_indexer_re = LazyReCompile( - r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\])\.(.*)''') + r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+)\.(.*)''') def current_array_with_indexer(cursor_offset, line): @@ -241,7 +241,7 @@ def current_array_with_indexer(cursor_offset, line): current_array_item_member_name_re = LazyReCompile( - r'''([\w_][\w0-9._]*\[[a-zA-Z0-9_"']+\]\.)(.*)''') + r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+\.)(.*)''') def current_array_item_member_name(cursor_offset, line): From ce1eb1aa2eb29c75a7e6a3a21c27e04dba4cc8b9 Mon Sep 17 00:00:00 2001 From: Shawn Axsom Date: Fri, 27 Nov 2015 18:59:28 +0000 Subject: [PATCH 0628/1650] Add tests --- bpython/autocomplete.py | 4 ++-- bpython/test/test_autocomplete.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 1cca269f4..58711955a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -335,7 +335,7 @@ def list_attributes(self, obj): return dir(obj) -class ArrayObjectMembersCompletion(BaseCompletionType): +class ArrayItemMembersCompletion(BaseCompletionType): def __init__(self, shown_before_tab=True, mode=SIMPLE): self._shown_before_tab = shown_before_tab @@ -603,7 +603,7 @@ def get_default_completer(mode=SIMPLE): MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), GlobalCompletion(mode=mode), - ArrayObjectMembersCompletion(mode=mode), + ArrayItemMembersCompletion(mode=mode), CumulativeCompleter((AttrCompletion(mode=mode), ParameterNameCompletion(mode=mode)), mode=mode) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 24018f3d2..d0e75f878 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -263,6 +263,22 @@ def __getattr__(self, attr): self.com.matches(4, 'a.__', locals_=locals_)) +class TestArrayItemCompletion(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.com = autocomplete.ArrayItemMembersCompletion() + + def test_att_matches_found_on_instance(self): + self.assertSetEqual(self.com.matches(5, 'a[0].', locals_={'a': [Foo()]}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + @skip_old_style + def test_att_matches_found_on_old_style_instance(self): + self.assertSetEqual(self.com.matches(5, 'a[0].', + locals_={'a': [OldStyleFoo()]}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + class TestMagicMethodCompletion(unittest.TestCase): def test_magic_methods_complete_after_double_underscores(self): From 58f3edfb26875450bbd71b909cb003f6b942bdab Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 28 Nov 2015 15:21:22 -0500 Subject: [PATCH 0629/1650] Allow global completion in brackets Make DictKeyCompletion return None when no matches found so other completers can take over. Perhaps ideal would be comulative, but this seems like good behavior -- it's clear to the user in most cases that what's now being completed are keys, then other completion. --- bpython/autocomplete.py | 9 +++++---- bpython/test/test_autocomplete.py | 15 ++++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 58711955a..1370a7fa4 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -387,12 +387,13 @@ def matches(self, cursor_offset, line, **kwargs): try: obj = safe_eval(dexpr, locals_) except EvaluationError: - return set() + return None if isinstance(obj, dict) and obj.keys(): - return set("{0!r}]".format(k) for k in obj.keys() - if repr(k).startswith(r.word)) + matches = set("{0!r}]".format(k) for k in obj.keys() + if repr(k).startswith(r.word)) + return matches if matches else None else: - return set() + return None def locate(self, current_offset, line): return lineparts.current_dict_key(current_offset, line) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index d0e75f878..a8ec672de 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -190,20 +190,25 @@ def test_set_of_keys_returned_when_matches_found(self): self.assertSetEqual(com.matches(2, "d[", locals_=local), set(["'ab']", "'cd']"])) - def test_empty_set_returned_when_eval_error(self): + def test_none_returned_when_eval_error(self): com = autocomplete.DictKeyCompletion() local = {'e': {"ab": 1, "cd": 2}} - self.assertSetEqual(com.matches(2, "d[", locals_=local), set()) + self.assertEqual(com.matches(2, "d[", locals_=local), None) - def test_empty_set_returned_when_not_dict_type(self): + def test_none_returned_when_not_dict_type(self): com = autocomplete.DictKeyCompletion() local = {'l': ["ab", "cd"]} - self.assertSetEqual(com.matches(2, "l[", locals_=local), set()) + self.assertEqual(com.matches(2, "l[", locals_=local), None) + + def test_none_returned_when_no_matches_left(self): + com = autocomplete.DictKeyCompletion() + local = {'d': {"ab": 1, "cd": 2}} + self.assertEqual(com.matches(3, "d[r", locals_=local), None) def test_obj_that_does_not_allow_conversion_to_bool(self): com = autocomplete.DictKeyCompletion() local = {'mNumPy': MockNumPy()} - self.assertSetEqual(com.matches(7, "mNumPy[", locals_=local), set()) + self.assertEqual(com.matches(7, "mNumPy[", locals_=local), None) class Foo(object): From 05c83beb32b1c754e127fe40f8e09c7d2d3abf4d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 28 Nov 2015 18:39:12 -0500 Subject: [PATCH 0630/1650] Avoid unsafe completion on array elements This commit removes functionality it would be nice to reimplement but wasn't safe: * completion on nested arrays ( list[1][2].a ) * completion on subclasses of list, tuple etc. --- bpython/autocomplete.py | 43 +++++++++++++++++++++------- bpython/line.py | 39 ++++++++++++++++--------- bpython/test/test_autocomplete.py | 26 +++++++++++++++++ bpython/test/test_line_properties.py | 40 ++++++++++++++++++++++++-- 4 files changed, 122 insertions(+), 26 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 1370a7fa4..e1c34090d 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -118,8 +118,10 @@ def matches(self, cursor_offset, line, **kwargs): raise NotImplementedError def locate(self, cursor_offset, line): - """Returns a start, stop, and word given a line and cursor, or None - if no target for this type of completion is found under the cursor""" + """Returns a Linepart namedtuple instance or None given cursor and line + + A Linepart namedtuple contains a start, stop, and word. None is returned + if no target for this type of completion is found under the cursor.""" raise NotImplementedError def format(self, word): @@ -346,28 +348,47 @@ def matches(self, cursor_offset, line, **kwargs): return None locals_ = kwargs['locals_'] - r = self.locate(cursor_offset, line) - if r is None: + full = self.locate(cursor_offset, line) + if full is None: return None - member_part = r[2] - _, _, dexpr = lineparts.current_array_with_indexer(cursor_offset, line) + + arr = lineparts.current_indexed_member_access_identifier( + cursor_offset, line) + index = lineparts.current_indexed_member_access_identifier_with_index( + cursor_offset, line) + member = lineparts.current_indexed_member_access_member( + cursor_offset, line) + try: - locals_['temp_val_from_array'] = safe_eval(dexpr, locals_) + obj = safe_eval(arr.word, locals_) + except EvaluationError: + return None + if type(obj) not in (list, tuple) + string_types: + # then is may be unsafe to do attribute lookup on it + return None + + try: + locals_['temp_val_from_array'] = safe_eval(index.word, locals_) except (EvaluationError, IndexError): - return set() + return None - temp_line = line.replace(member_part, 'temp_val_from_array.') + temp_line = line.replace(index.word, 'temp_val_from_array.') matches = self.completer.matches(len(temp_line), temp_line, **kwargs) + if matches is None: + return None + matches_with_correct_name = \ - set(match.replace('temp_val_from_array.', member_part) for match in matches) + set(match.replace('temp_val_from_array.', index.word+'.') + for match in matches if match[20:].startswith(member.word)) del locals_['temp_val_from_array'] return matches_with_correct_name def locate(self, current_offset, line): - return lineparts.current_array_item_member_name(current_offset, line) + a = lineparts.current_indexed_member_access(current_offset, line) + return a def format(self, match): return after_last_dot(match) diff --git a/bpython/line.py b/bpython/line.py index 15495d988..17599a5e1 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -228,25 +228,38 @@ def current_string_literal_attr(cursor_offset, line): return None -current_array_with_indexer_re = LazyReCompile( - r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+)\.(.*)''') +current_indexed_member_re = LazyReCompile( + r'''([a-zA-Z_][\w.]*)\[([a-zA-Z0-9_"']+)\]\.([\w.]*)''') -def current_array_with_indexer(cursor_offset, line): - """an array and indexer, e.g. foo[1]""" - matches = current_array_with_indexer_re.finditer(line) +def current_indexed_member_access(cursor_offset, line): + """An identifier being indexed and member accessed""" + matches = current_indexed_member_re.finditer(line) for m in matches: - if m.start(1) <= cursor_offset and m.end(1) <= cursor_offset: + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: + return LinePart(m.start(1), m.end(3), m.group()) + + +def current_indexed_member_access_identifier(cursor_offset, line): + """An identifier being indexed, e.g. foo in foo[1].bar""" + matches = current_indexed_member_re.finditer(line) + for m in matches: + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: return LinePart(m.start(1), m.end(1), m.group(1)) -current_array_item_member_name_re = LazyReCompile( - r'''([\w_][\w0-9._]*(?:\[[a-zA-Z0-9_"']+\])+\.)(.*)''') +def current_indexed_member_access_identifier_with_index(cursor_offset, line): + """An identifier being indexed with the index, e.g. foo[1] in foo[1].bar""" + matches = current_indexed_member_re.finditer(line) + for m in matches: + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: + return LinePart(m.start(1), m.end(2)+1, + "%s[%s]" % (m.group(1), m.group(2))) -def current_array_item_member_name(cursor_offset, line): - """the member name after an array indexer, e.g. foo[1].bar""" - matches = current_array_item_member_name_re.finditer(line) +def current_indexed_member_access_member(cursor_offset, line): + """The member name of an indexed object, e.g. bar in foo[1].bar""" + matches = current_indexed_member_re.finditer(line) for m in matches: - if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: - return LinePart(m.start(1), m.end(2), m.group(1)) + if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: + return LinePart(m.start(3), m.end(3), m.group(3)) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index a8ec672de..37a52002d 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -283,6 +283,32 @@ def test_att_matches_found_on_old_style_instance(self): locals_={'a': [OldStyleFoo()]}), set(['a[0].method', 'a[0].a', 'a[0].b'])) + def test_other_getitem_methods_not_called(self): + class FakeList(object): + def __getitem__(inner_self, i): + self.fail("possibly side-effecting __getitem_ method called") + + self.com.matches(5, 'a[0].', locals_={'a': FakeList()}) + + def test_tuples_complete(self): + self.assertSetEqual(self.com.matches(5, 'a[0].', + locals_={'a': (Foo(),)}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + @unittest.skip('TODO, subclasses do not complete yet') + def test_list_subclasses_complete(self): + class ListSubclass(list): pass + self.assertSetEqual(self.com.matches(5, 'a[0].', + locals_={'a': ListSubclass([Foo()])}), + set(['a[0].method', 'a[0].a', 'a[0].b'])) + + def test_getitem_not_called_in_list_subclasses_overriding_getitem(self): + class FakeList(list): + def __getitem__(inner_self, i): + self.fail("possibly side-effecting __getitem_ method called") + + self.com.matches(5, 'a[0].', locals_={'a': FakeList()}) + class TestMagicMethodCompletion(unittest.TestCase): diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 26eee9a07..7ea49f5d8 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -5,7 +5,9 @@ current_string, current_object, current_object_attribute, \ current_from_import_from, current_from_import_import, current_import, \ current_method_definition_name, current_single_word, \ - current_string_literal_attr + current_string_literal_attr, current_indexed_member_access_identifier, \ + current_indexed_member_access_identifier_with_index, \ + current_indexed_member_access_member def cursor(s): @@ -20,7 +22,7 @@ def decode(s): if not s.count('|') == 1: raise ValueError('match helper needs | to occur once') - if s.count('<') != s.count('>') or not s.count('<') in (0, 1): + if s.count('<') != s.count('>') or s.count('<') not in (0, 1): raise ValueError('match helper needs <, and > to occur just once') matches = list(re.finditer(r'[<>|]', s)) assert len(matches) in [1, 3], [m.group() for m in matches] @@ -305,6 +307,40 @@ def test_simple(self): self.assertAccess('"hey".asdf d|') self.assertAccess('"hey".<|>') +class TestCurrentIndexedMemberAccessIdentifier(LineTestCase): + def setUp(self): + self.func = current_indexed_member_access_identifier + + def test_simple(self): + self.assertAccess('[def].ghi|') + self.assertAccess('[def].|ghi') + self.assertAccess('[def].gh|i') + self.assertAccess('abc[def].gh |i') + self.assertAccess('abc[def]|') + + +class TestCurrentIndexedMemberAccessIdentifierWithIndex(LineTestCase): + def setUp(self): + self.func = current_indexed_member_access_identifier_with_index + + def test_simple(self): + self.assertAccess('.ghi|') + self.assertAccess('.|ghi') + self.assertAccess('.gh|i') + self.assertAccess('abc[def].gh |i') + self.assertAccess('abc[def]|') + + +class TestCurrentIndexedMemberAccessMember(LineTestCase): + def setUp(self): + self.func = current_indexed_member_access_member + + def test_simple(self): + self.assertAccess('abc[def].') + self.assertAccess('abc[def].<|ghi>') + self.assertAccess('abc[def].') + self.assertAccess('abc[def].gh |i') + self.assertAccess('abc[def]|') if __name__ == '__main__': unittest.main() From dc5e87fd91f3e4377f7623c2b7ed72e4b7cecc53 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 29 Nov 2015 23:29:53 +0100 Subject: [PATCH 0631/1650] Log exceptions from auto completers Also do not catch KeyboardInterrupt and SystemExit. Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 762a946c0..91818d4a5 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -29,6 +29,7 @@ import abc import glob import keyword +import logging import os import re import rlcompleter @@ -609,13 +610,18 @@ def get_completer(completers, cursor_offset, line, **kwargs): double underscore methods like __len__ in method signatures """ - try: - for completer in completers: + for completer in completers: + try: matches = completer.matches(cursor_offset, line, **kwargs) - if matches is not None: - return sorted(matches), (completer if matches else None) - except: - pass + except Exception as e: + # Instead of crashing the UI, log exceptions from autocompleters. + logger = logging.getLogger(__name__) + logger.debug( + 'Completer {} failed with unhandled exception: {}'.format( + completer, e)) + continue + if matches is not None: + return sorted(matches), (completer if matches else None) return [], None From 404e5c718ebc3e58a655f65404aef36651da7f26 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 29 Nov 2015 23:39:15 +0100 Subject: [PATCH 0632/1650] No longer install Twisted for 2.6 Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index dadbd2373..f1fd0c7f8 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -20,15 +20,12 @@ if [[ $RUN == nosetests ]]; then # Python 2.6 specific dependencies if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2 + elif [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then + # dependencies for crasher tests + pip install Twisted urwid fi case $TRAVIS_PYTHON_VERSION in - 2*) - # dependencies for crasher tests - pip install Twisted urwid - # test specific dependencies - pip install mock - ;; - pypy) + 2*|pypy) # test specific dependencies pip install mock ;; From 599cfea433f60f750a3c019add211353a7bba4bc Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 29 Nov 2015 14:51:44 -0500 Subject: [PATCH 0633/1650] fix parameter name completion --- bpython/autocomplete.py | 14 +++++++------- bpython/test/test_autocomplete.py | 4 ++-- bpython/test/test_repl.py | 15 ++++++++++++++- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 91818d4a5..070aa981b 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -160,17 +160,17 @@ def format(self, word): return self._completers[0].format(word) def matches(self, cursor_offset, line, **kwargs): + return_value = None all_matches = set() for completer in self._completers: - # these have to be explicitely listed to deal with the different - # signatures of various matches() methods of completers matches = completer.matches(cursor_offset=cursor_offset, line=line, **kwargs) if matches is not None: all_matches.update(matches) + return_value = all_matches - return all_matches + return return_value class ImportCompletion(BaseCompletionType): @@ -634,11 +634,11 @@ def get_default_completer(mode=SIMPLE): FilenameCompletion(mode=mode), MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), - GlobalCompletion(mode=mode), - ArrayItemMembersCompletion(mode=mode), - CumulativeCompleter((AttrCompletion(mode=mode), + CumulativeCompleter((GlobalCompletion(mode=mode), ParameterNameCompletion(mode=mode)), - mode=mode) + mode=mode), + ArrayItemMembersCompletion(mode=mode), + AttrCompletion(mode=mode), ) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 37a52002d..76c519511 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -107,10 +107,10 @@ def test_one_empty_completer_returns_empty(self): cumulative = autocomplete.CumulativeCompleter([a]) self.assertEqual(cumulative.matches(3, 'abc'), set()) - def test_one_none_completer_returns_empty(self): + def test_one_none_completer_returns_none(self): a = self.completer(None) cumulative = autocomplete.CumulativeCompleter([a]) - self.assertEqual(cumulative.matches(3, 'abc'), set()) + self.assertEqual(cumulative.matches(3, 'abc'), None) def test_two_completers_get_both(self): a = self.completer(['a']) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 08ed7564c..acd3a6889 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -383,7 +383,7 @@ def test_fuzzy_attribute_complete(self): self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertEqual(self.repl.matches_iter.matches, ['Foo.bar']) - # 3. Edge Cases + # 3. Edge cases def test_updating_namespace_complete(self): self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) self.set_input_line("foo") @@ -400,6 +400,19 @@ def test_file_should_not_appear_in_complete(self): self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) self.assertNotIn('__file__', self.repl.matches_iter.matches) + # 4. Parameter names + def test_paremeter_name_completion(self): + self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) + self.set_input_line("foo(ab") + + code = "def foo(abc=1, abd=2, xyz=3):\n\tpass\n" + for line in code.split("\n"): + self.repl.push(line) + + self.assertTrue(self.repl.complete()) + self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) + self.assertEqual(self.repl.matches_iter.matches, ['abc=', 'abd=', 'abs(']) + class TestCliRepl(unittest.TestCase): From 575cafaab9bf7f0339dd1541f6c6ce8b8885d9dd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 1 Dec 2015 10:45:46 +0100 Subject: [PATCH 0634/1650] Update appdata to latest spec Signed-off-by: Sebastian Ramacher --- data/bpython.appdata.xml | 43 ++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/data/bpython.appdata.xml b/data/bpython.appdata.xml index b24c09368..e3d5d32c9 100644 --- a/data/bpython.appdata.xml +++ b/data/bpython.appdata.xml @@ -1,8 +1,8 @@ - + - - bpython.desktop + + bpython.desktop CC0-1.0 MIT bpython interpreter @@ -23,15 +23,32 @@

http://www.bpython-interpreter.org/ + https://github.com/bpython/bpython/issues - http://bpython-interpreter.org/images/8.png - http://bpython-interpreter.org/images/1.png - http://bpython-interpreter.org/images/2.png - http://bpython-interpreter.org/images/3.png - http://bpython-interpreter.org/images/4.png - http://bpython-interpreter.org/images/5.png - http://bpython-interpreter.org/images/6.png - http://bpython-interpreter.org/images/7.png + + http://bpython-interpreter.org/images/8.png + + + http://bpython-interpreter.org/images/1.png + + + http://bpython-interpreter.org/images/2.png + + + http://bpython-interpreter.org/images/3.png + + + http://bpython-interpreter.org/images/4.png + + + http://bpython-interpreter.org/images/5.png + + + http://bpython-interpreter.org/images/6.png + + + http://bpython-interpreter.org/images/7.png + - bpython@googlegroups.com -
+ bpython@googlegroups.com + From a108ee1b57426f53886664c808d037f09cd0f7b1 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 3 Dec 2015 11:22:43 -0500 Subject: [PATCH 0635/1650] fix run order-dependant test failure --- bpython/test/test_curtsies_repl.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 1e405bffc..8ff3b0908 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -20,6 +20,12 @@ from bpython.test import (FixLanguageTestCase as TestCase, MagicIterMock, mock, builtin_target, unittest) +if py3: + from importlib import invalidate_caches +else: + def invalidate_caches(): + """Does not exist before Python 3.3""" + def setup_config(conf): config_struct = config.Struct() @@ -291,6 +297,7 @@ def setUp(self): self.open = partial(io.open, mode='wt', encoding='utf-8') self.dont_write_bytecode = sys.dont_write_bytecode sys.dont_write_bytecode = True + invalidate_caches() def tearDown(self): sys.dont_write_bytecode = self.dont_write_bytecode From 78e1f9ba3261fbc8e710ee9832d2a7b223146627 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 5 Dec 2015 19:24:51 -0500 Subject: [PATCH 0636/1650] fix #505 blank bytestring line in status bar --- bpython/curtsiesfrontend/interaction.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 8062bcddb..3022ba430 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import greenlet import time import curtsies.events as events From eefbff476c2ca022fb3718c5d29853eda40ba676 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 5 Dec 2015 19:13:10 -0500 Subject: [PATCH 0637/1650] move paste detection to bpython --- bpython/curtsies.py | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 91843120e..a5886868d 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import code +import collections import io import logging import sys @@ -91,10 +92,44 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): return extract_exit_value(exit_value) +def _combined_events(event_provider, paste_threshold): + """Combines consecutive keypress events into paste events.""" + timeout = (yield 'nonsense_event') # so send can be used + queue = collections.deque() + while True: + e = event_provider.send(timeout) + if isinstance(e, curtsies.events.Event): + timeout = (yield e) + continue + elif e is None: + continue + else: + queue.append(e) + e = event_provider.send(0) + while not (e is None or isinstance(e, curtsies.events.Event)): + queue.append(e) + e = event_provider.send(0) + if len(queue) >= paste_threshold: + paste = curtsies.events.PasteEvent() + paste.events.extend(queue) + queue.clear() + yield paste + else: + while len(queue): + yield queue.popleft() + + +def combined_events(event_provider, paste_threshold=10): + g = _combined_events(event_provider, paste_threshold) + next(g) + return g + + def mainloop(config, locals_, banner, interp=None, paste=None, interactive=True): - with curtsies.input.Input(keynames='curtsies', sigint_event=True) as \ - input_generator: + with curtsies.input.Input(keynames='curtsies', + sigint_event=True, + paste_threshold=None) as input_generator: with curtsies.window.CursorAwareWindow( sys.stdout, sys.stdin, @@ -180,12 +215,13 @@ def process_event(e): # do a display before waiting for first event process_event(None) + inputs = combined_events(input_generator) for unused in find_iterator: - e = input_generator.send(0) + e = inputs.send(0) if e is not None: process_event(e) - for e in input_generator: + for e in inputs: process_event(e) From f10856417fcc41379c1a5f4f1ac1cee024f152af Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 5 Dec 2015 20:23:20 -0500 Subject: [PATCH 0638/1650] Test combining events for paste detection --- bpython/curtsies.py | 9 ++-- bpython/test/test_curtsies.py | 83 +++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 bpython/test/test_curtsies.py diff --git a/bpython/curtsies.py b/bpython/curtsies.py index a5886868d..964400ef1 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -94,14 +94,15 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): def _combined_events(event_provider, paste_threshold): """Combines consecutive keypress events into paste events.""" - timeout = (yield 'nonsense_event') # so send can be used + timeout = yield 'nonsense_event' # so send can be used immediately queue = collections.deque() while True: e = event_provider.send(timeout) if isinstance(e, curtsies.events.Event): - timeout = (yield e) + timeout = yield e continue elif e is None: + timeout = yield None continue else: queue.append(e) @@ -113,10 +114,10 @@ def _combined_events(event_provider, paste_threshold): paste = curtsies.events.PasteEvent() paste.events.extend(queue) queue.clear() - yield paste + timeout = yield paste else: while len(queue): - yield queue.popleft() + timeout = yield queue.popleft() def combined_events(event_provider, paste_threshold=10): diff --git a/bpython/test/test_curtsies.py b/bpython/test/test_curtsies.py new file mode 100644 index 000000000..cd0a780ca --- /dev/null +++ b/bpython/test/test_curtsies.py @@ -0,0 +1,83 @@ +# coding: utf-8 +from __future__ import unicode_literals + +from collections import namedtuple + +from bpython.curtsies import combined_events +from bpython.test import (FixLanguageTestCase as TestCase, unittest) + +import curtsies.events + + +ScheduledEvent = namedtuple('ScheduledEvent', ['when', 'event']) + + +class EventGenerator(object): + def __init__(self, initial_events=(), scheduled_events=()): + self._events = [] + self._current_tick = 0 + for e in initial_events: + self.schedule_event(e, 0) + for e, w in scheduled_events: + self.schedule_event(e, w) + + def schedule_event(self, event, when): + self._events.append(ScheduledEvent(when, event)) + self._events.sort() + + def send(self, timeout=None): + if timeout not in [None, 0]: + raise ValueError('timeout value %r not supported' % timeout) + if not self._events: + return None + if self._events[0].when <= self._current_tick: + return self._events.pop(0).event + + if timeout == 0: + return None + elif timeout is None: + e = self._events.pop(0) + self._current_tick = e.when + return e.event + else: + raise ValueError('timeout value %r not supported' % timeout) + + def tick(self, dt=1): + self._current_tick += dt + + +class TestCurtsiesPasteDetection(TestCase): + def test_paste_threshold(self): + inputs = combined_events(EventGenerator(list('abc'))) + cb = combined_events(inputs, paste_threshold=3) + e = next(cb) + self.assertIsInstance(e, curtsies.events.PasteEvent) + self.assertEqual(e.events, list('abc')) + self.assertEqual(next(cb), None) + + inputs = combined_events(EventGenerator(list('abc'))) + cb = combined_events(inputs, paste_threshold=4) + self.assertEqual(next(cb), 'a') + self.assertEqual(next(cb), 'b') + self.assertEqual(next(cb), 'c') + self.assertEqual(next(cb), None) + + def test_set_timeout(self): + eg = EventGenerator('a', zip('bcd', [1,2,3])) + inputs = combined_events(eg) + cb = combined_events(inputs, paste_threshold=5) + self.assertEqual(next(cb), 'a') + self.assertEqual(cb.send(0), None) + self.assertEqual(next(cb), 'b') + self.assertEqual(cb.send(0), None) + eg.tick() + self.assertEqual(cb.send(0), 'c') + self.assertEqual(cb.send(0), None) + eg.tick() + self.assertEqual(cb.send(0), 'd') + self.assertEqual(cb.send(0), None) + + + +if __name__ == '__main__': + unittest.main() From b1f22f545c350461d0e6dc641088dcb14392bbfa Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 5 Dec 2015 20:54:11 -0500 Subject: [PATCH 0639/1650] increase coverage of paste detector --- bpython/test/test_curtsies.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_curtsies.py b/bpython/test/test_curtsies.py index cd0a780ca..0fcad6adb 100644 --- a/bpython/test/test_curtsies.py +++ b/bpython/test/test_curtsies.py @@ -44,6 +44,7 @@ def send(self, timeout=None): def tick(self, dt=1): self._current_tick += dt + return self._current_tick class TestCurtsiesPasteDetection(TestCase): @@ -63,9 +64,11 @@ def test_paste_threshold(self): self.assertEqual(next(cb), None) def test_set_timeout(self): - eg = EventGenerator('a', zip('bcd', [1,2,3])) + eg = EventGenerator('a', zip('bcdefg', [1, 2, 3, 3, 3, 4])) + eg.schedule_event(curtsies.events.SigIntEvent(), 5) + eg.schedule_event('h', 6) inputs = combined_events(eg) - cb = combined_events(inputs, paste_threshold=5) + cb = combined_events(inputs, paste_threshold=3) self.assertEqual(next(cb), 'a') self.assertEqual(cb.send(0), None) self.assertEqual(next(cb), 'b') @@ -74,9 +77,15 @@ def test_set_timeout(self): self.assertEqual(cb.send(0), 'c') self.assertEqual(cb.send(0), None) eg.tick() - self.assertEqual(cb.send(0), 'd') + self.assertIsInstance(cb.send(0), curtsies.events.PasteEvent) self.assertEqual(cb.send(0), None) - + self.assertEqual(cb.send(None), 'g') + self.assertEqual(cb.send(0), None) + eg.tick(1) + self.assertIsInstance(cb.send(0), curtsies.events.SigIntEvent) + self.assertEqual(cb.send(0), None) + self.assertEqual(cb.send(None), 'h') + self.assertEqual(cb.send(None), None) if __name__ == '__main__': From 4e92abd1613583b86dab75fb02d317cf4045cc4b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 6 Dec 2015 11:25:07 -0500 Subject: [PATCH 0640/1650] update changelog --- CHANGELOG | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c8c4bd714..fb7ebdda7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,10 +7,27 @@ Changelog New features: * #425: Added curtsies 0.2.x support. +* Attribute completion on items in lists and tuples +* #528: Hide private attribute from initial autocompletion suggestions. + Thanks to Jeppe Toustrup. +* #538: Multi-line banners are allowed. +* #229: inspect.getsource works on interactively defined functions. + Thanks to Michael Mulley. +* Ctrl-e can be used to autocomplete current fish-style suggestion. + Thanks to Amjith Ramanujam Fixes: * #484: Switch `bpython.embed` to the curtsies frontend. +* Exceptions in autcompletion are now logged instead of crashing bpython. +* #548 fix transpose character bug. Thanks to Wes E. Vial. +* #527 -q disables version banner. +* #544 fix Jedi completion error +* #536 fix completion on old-style classes with custom __getattr__ +* #480 fix old-style class autocompletion. Thanks to Joe Jevnik. +* Fix reload in Python 3. Thanks to sharow. +* Fix keyword agument parameter name completion + 0.14.2 ------ From 9b6aa7bf29698056ee01d8b0e1d59554a0a0886f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 6 Dec 2015 15:58:35 -0500 Subject: [PATCH 0641/1650] enable attribute completion within function call --- bpython/autocomplete.py | 4 ++-- bpython/test/test_autocomplete.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 070aa981b..bc67c7f4d 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -471,7 +471,7 @@ def matches(self, cursor_offset, line, **kwargs): if (self.method_match(word, n, r.word) and word != "__builtins__"): matches.add(_callable_postfix(val, word)) - return matches + return matches if matches else None def locate(self, current_offset, line): return lineparts.current_single_word(current_offset, line) @@ -496,7 +496,7 @@ def matches(self, cursor_offset, line, **kwargs): if py3: matches.update(name + '=' for name in argspec[1][4] if name.startswith(r.word)) - return matches + return matches if matches else None def locate(self, current_offset, line): return lineparts.current_word(current_offset, line) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 76c519511..c713d363c 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -383,16 +383,16 @@ def test_completions_are_unicode(self): @unittest.skipIf(py3, "in Python 3 invalid identifiers are passed through") def test_ignores_nonascii_encodable(self): - self.assertSetEqual(self.com.matches(3, 'abc', locals_={'abcß': 10}), - set()) + self.assertEqual(self.com.matches(3, 'abc', locals_={'abcß': 10}), + None) def test_mock_kwlist(self): with mock.patch.object(keyword, 'kwlist', new=['abcd']): - self.assertSetEqual(self.com.matches(3, 'abc', locals_={}), set()) + self.assertEqual(self.com.matches(3, 'abc', locals_={}), None) def test_mock_kwlist_non_ascii(self): with mock.patch.object(keyword, 'kwlist', new=['abcß']): - self.assertSetEqual(self.com.matches(3, 'abc', locals_={}), set()) + self.assertEqual(self.com.matches(3, 'abc', locals_={}), None) class TestParameterNameCompletion(unittest.TestCase): From 9966986583cf4de5201b34943332922f5b80bcd5 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 6 Dec 2015 16:18:54 -0500 Subject: [PATCH 0642/1650] Document why invalidate_caches needed --- bpython/test/test_curtsies_repl.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 8ff3b0908..1d408c5e6 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -297,6 +297,19 @@ def setUp(self): self.open = partial(io.open, mode='wt', encoding='utf-8') self.dont_write_bytecode = sys.dont_write_bytecode sys.dont_write_bytecode = True + + # Because these tests create Python source files at runtime, + # it's possible for the importlib.machinery.FileFinder for + # a directory to have an outdated cache in the following situation: + # * a module in that directory is imported, + # * then a new module is created in that directory, + # * then that new module is imported. + # + # invalidate_cache() is used to prevent this. + # + # see https://docs.python.org/3/library/importlib.html + # sections #importlib.machinery.FileFinder and + # #importlib.invalidate_caches invalidate_caches() def tearDown(self): From 460d7f0e3a68fdfd5003e4cb1aa42ba93e65dc97 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 6 Dec 2015 23:34:30 -0500 Subject: [PATCH 0643/1650] fix #583 --- bpython/repl.py | 2 +- bpython/test/test_repl.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index dc13b2f6e..5bb694ed2 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -579,7 +579,7 @@ def set_docstring(self): self.docstring = None if not self.get_args(): self.funcprops = None - elif self.current_func is not None: + if self.current_func is not None: try: self.docstring = pydoc.getdoc(self.current_func) except IndexError: diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index acd3a6889..b1d186ee0 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -222,6 +222,13 @@ def test_issue572(self): self.set_input_line("WonderfulSpam(") self.assertTrue(self.repl.get_args()) + def test_issue583(self): + self.repl = FakeRepl() + self.repl.push("a = 1.2\n", False) + self.set_input_line("a.is_integer(") + self.repl.set_docstring() + self.assertIsNot(self.repl.docstring, None) + class TestGetSource(unittest.TestCase): def setUp(self): From 6ab499b41daa1578131d75ed68e5d46a57a8c9e9 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 7 Dec 2015 22:31:07 -0500 Subject: [PATCH 0644/1650] ignore completion until the end of a paste event --- bpython/curtsiesfrontend/repl.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index a5f5dfa03..1c01a0ea2 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1311,6 +1311,8 @@ def in_paste_mode(self): self.paste_mode = True yield self.paste_mode = orig_value + if not self.paste_mode: + self.update_completion() # Debugging shims, good example of embedding a Repl in other code def dumb_print_output(self): @@ -1375,6 +1377,8 @@ def _set_current_line(self, line, update_completion=True, if self._current_line == line: return self._current_line = line + if self.paste_mode: + return if update_completion: self.update_completion() if reset_rl_history: @@ -1392,6 +1396,10 @@ def _set_cursor_offset(self, offset, update_completion=True, reset_rl_history=False, clear_special_mode=True): if self._cursor_offset == offset: return + if self.paste_mode: + self._cursor_offset = offset + self.unhighlight_paren() + return if reset_rl_history: self.rl_history.reset() if clear_special_mode: From e6916d1c2da9baf7c7e31e04ed13920854689a00 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 10 Dec 2015 08:12:57 -0500 Subject: [PATCH 0645/1650] more information in invalidate_caches comment --- bpython/test/test_curtsies_repl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 1d408c5e6..dbfc6796f 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -299,13 +299,13 @@ def setUp(self): sys.dont_write_bytecode = True # Because these tests create Python source files at runtime, - # it's possible for the importlib.machinery.FileFinder for - # a directory to have an outdated cache in the following situation: + # it's possible in Python >=3.3 for the importlib.machinery.FileFinder + # for a directory to have an outdated cache when # * a module in that directory is imported, # * then a new module is created in that directory, # * then that new module is imported. - # - # invalidate_cache() is used to prevent this. + # Automatic cache invalidation is based on the second-resolution mtime + # of the directory, so we need to manually call invalidate_caches(). # # see https://docs.python.org/3/library/importlib.html # sections #importlib.machinery.FileFinder and From 2a9b51ffd9898c08736acbd2486481350f366d85 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 10 Dec 2015 16:54:54 -0500 Subject: [PATCH 0646/1650] add #506 to changelog --- CHANGELOG | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index fb7ebdda7..a4e6be1bf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,24 +7,25 @@ Changelog New features: * #425: Added curtsies 0.2.x support. -* Attribute completion on items in lists and tuples * #528: Hide private attribute from initial autocompletion suggestions. Thanks to Jeppe Toustrup. * #538: Multi-line banners are allowed. * #229: inspect.getsource works on interactively defined functions. Thanks to Michael Mulley. +* Attribute completion on items in lists and tuples * Ctrl-e can be used to autocomplete current fish-style suggestion. Thanks to Amjith Ramanujam Fixes: * #484: Switch `bpython.embed` to the curtsies frontend. -* Exceptions in autcompletion are now logged instead of crashing bpython. * #548 fix transpose character bug. Thanks to Wes E. Vial. * #527 -q disables version banner. * #544 fix Jedi completion error * #536 fix completion on old-style classes with custom __getattr__ * #480 fix old-style class autocompletion. Thanks to Joe Jevnik. +* #506 in python -i mod.py sys.modules[__name__] refers to module dict +* Exceptions in autcompletion are now logged instead of crashing bpython. * Fix reload in Python 3. Thanks to sharow. * Fix keyword agument parameter name completion From 6df6a4a4004557886a17bcfcd93d6a5f3492843d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 11 Dec 2015 09:23:34 -0500 Subject: [PATCH 0647/1650] set paste threshold at 3 keypress events --- bpython/curtsies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 964400ef1..0855eb38b 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -120,7 +120,7 @@ def _combined_events(event_provider, paste_threshold): timeout = yield queue.popleft() -def combined_events(event_provider, paste_threshold=10): +def combined_events(event_provider, paste_threshold=3): g = _combined_events(event_provider, paste_threshold) next(g) return g From fa08b7400d50920d5ed6f89ff7168c5e51f1d469 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 11 Dec 2015 10:55:32 -0500 Subject: [PATCH 0648/1650] small paste events may contain like events --- bpython/curtsiesfrontend/repl.py | 43 +++++++++++++++++++++++------- bpython/test/test_curtsies.py | 11 ++++---- bpython/test/test_curtsies_repl.py | 26 ++++++++++++++++++ 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index a5f5dfa03..98ec53ce9 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -84,6 +84,9 @@ Press {config.edit_config_key} to edit this config file. """ EXAMPLE_CONFIG_URL = 'https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config' +MAX_EVENTS_POSSIBLY_NOT_PASTE = 20 # more than this many events will be assumed to + # be a true paste event, i.e. control characters + # like '' will be stripped # This is needed for is_nop and should be removed once is_nop is fixed. if py3: @@ -545,16 +548,25 @@ def process_control_event(self, e): ctrl_char = compress_paste_event(e) if ctrl_char is not None: return self.process_event(ctrl_char) - simple_events = just_simple_events(e.events) - source = preprocess(''.join(simple_events), - self.interp.compile) - with self.in_paste_mode(): - for ee in source: - if self.stdin.has_focus: - self.stdin.process_event(ee) - else: - self.process_simple_keypress(ee) + # Might not really be a paste, UI might just be lagging + if (len(e.events) <= MAX_EVENTS_POSSIBLY_NOT_PASTE and + any(not is_simple_event(ee) for ee in e.events)): + for ee in e.events: + if self.stdin.has_focus: + self.stdin.process_event(ee) + else: + self.process_event(ee) + else: + simple_events = just_simple_events(e.events) + source = preprocess(''.join(simple_events), + self.interp.compile) + for ee in source: + if self.stdin.has_focus: + self.stdin.process_event(ee) + else: + self.process_simple_keypress(ee) + elif isinstance(e, bpythonevents.RunStartupFileEvent): try: @@ -1611,11 +1623,24 @@ def just_simple_events(event_list): pass # ignore events elif e == '': simple_events.append(' ') + elif len(e) > 1: + pass # get rid of etc. else: simple_events.append(e) return simple_events +def is_simple_event(e): + if isinstance(e, events.Event): + return False + if e in ("", "", "", "\n", "\r", ""): + return True + if len(e) > 1: + return False + else: + return True + + # TODO this needs some work to function again and be useful for embedding def simple_repl(): refreshes = [] diff --git a/bpython/test/test_curtsies.py b/bpython/test/test_curtsies.py index 0fcad6adb..4ea41a7e0 100644 --- a/bpython/test/test_curtsies.py +++ b/bpython/test/test_curtsies.py @@ -49,15 +49,15 @@ def tick(self, dt=1): class TestCurtsiesPasteDetection(TestCase): def test_paste_threshold(self): - inputs = combined_events(EventGenerator(list('abc'))) - cb = combined_events(inputs, paste_threshold=3) + eg = EventGenerator(list('abc')) + cb = combined_events(eg, paste_threshold=3) e = next(cb) self.assertIsInstance(e, curtsies.events.PasteEvent) self.assertEqual(e.events, list('abc')) self.assertEqual(next(cb), None) - inputs = combined_events(EventGenerator(list('abc'))) - cb = combined_events(inputs, paste_threshold=4) + eg = EventGenerator(list('abc')) + cb = combined_events(eg, paste_threshold=4) self.assertEqual(next(cb), 'a') self.assertEqual(next(cb), 'b') self.assertEqual(next(cb), 'c') @@ -67,8 +67,7 @@ def test_set_timeout(self): eg = EventGenerator('a', zip('bcdefg', [1, 2, 3, 3, 3, 4])) eg.schedule_event(curtsies.events.SigIntEvent(), 5) eg.schedule_event('h', 6) - inputs = combined_events(eg) - cb = combined_events(inputs, paste_threshold=3) + cb = combined_events(eg, paste_threshold=3) self.assertEqual(next(cb), 'a') self.assertEqual(cb.send(0), None) self.assertEqual(next(cb), 'b') diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 1e405bffc..1b5ef89be 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -20,6 +20,8 @@ from bpython.test import (FixLanguageTestCase as TestCase, MagicIterMock, mock, builtin_target, unittest) +from curtsies import events + def setup_config(conf): config_struct = config.Struct() @@ -402,5 +404,29 @@ def test_startup_event_latin1(self): self.assertIn('a', self.repl.interp.locals) +class TestCurtsiesPasteEvents(TestCase): + + def setUp(self): + self.repl = create_repl() + + def test_control_events_in_small_paste(self): + self.assertGreaterEqual(curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE, 6, + 'test assumes UI lag could cause 6 events') + p = events.PasteEvent() + p.events = ['a', 'b', 'c', 'd', '', 'e'] + self.repl.process_event(p) + self.assertEqual(self.repl.current_line, 'eabcd') + + + def test_control_events_in_large_paste(self): + """Large paste events should ignore control characters""" + p = events.PasteEvent() + p.events = (['a', ''] + + ['e'] * curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE) + self.repl.process_event(p) + self.assertEqual(self.repl.current_line, + 'a' + 'e'*curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE) + + if __name__ == '__main__': unittest.main() From 8aa42c3f815d84f18538e7174250ee7110713b71 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 12 Dec 2015 18:06:39 -0500 Subject: [PATCH 0649/1650] Don't reevaluate session if not modified --- bpython/curtsiesfrontend/repl.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index db55333cb..0577de989 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -84,6 +84,10 @@ Press {config.edit_config_key} to edit this config file. """ EXAMPLE_CONFIG_URL = 'https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config' +EDIT_SESSION_HEADER = ("### current bpython session - file will be " + "reevaluated, ### lines will not be run\n" + "### To return to bpython without reevaluating, " + "exit without making changes.\n") MAX_EVENTS_POSSIBLY_NOT_PASTE = 20 # more than this many events will be assumed to # be a true paste event, i.e. control characters # like '' will be stripped @@ -837,8 +841,7 @@ def send_current_block_to_external_editor(self, filename=None): self.cursor_offset = len(self.current_line) def send_session_to_external_editor(self, filename=None): - for_editor = ("### current bpython session - file will be " - "reevaluated, ### lines will not be run\n") + for_editor = EDIT_SESSION_HEADER for_editor += '\n'.join(line[len(self.ps1):] if line.startswith(self.ps1) else line[len(self.ps2):] @@ -846,13 +849,18 @@ def send_session_to_external_editor(self, filename=None): '### '+line for line in self.getstdout().split('\n')) text = self.send_to_external_editor(for_editor) + if text == for_editor: + self.status_bar.message( + _('Session not reevaluated because it was not edited')) + return lines = text.split('\n') - from_editor = [line for line in lines if line[:4] != '### '] + from_editor = [line for line in lines if line[:3] != '###'] source = preprocess('\n'.join(from_editor), self.interp.compile) self.history = source.split('\n') self.reevaluate(insert_into_history=True) self.current_line = lines[-1][4:] self.cursor_offset = len(self.current_line) + self.status_bar.message(_('Session edited and reevaluated')) def clear_modules_and_reevaluate(self): if self.watcher: From 08c50172134a26aace388fd69c538f050af84a50 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 13 Dec 2015 14:06:09 -0500 Subject: [PATCH 0650/1650] Fix currentline in reevaluate session --- bpython/curtsiesfrontend/repl.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 0577de989..0082f7ef9 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -84,10 +84,11 @@ Press {config.edit_config_key} to edit this config file. """ EXAMPLE_CONFIG_URL = 'https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config' -EDIT_SESSION_HEADER = ("### current bpython session - file will be " - "reevaluated, ### lines will not be run\n" - "### To return to bpython without reevaluating, " - "exit without making changes.\n") +EDIT_SESSION_HEADER = """### current bpython session - make changes and save to reevaluate session. +### lines beginning with ### will be ignored. +### To return to bpython without reevaluating make no changes to this file +### or save an empty file. +""" MAX_EVENTS_POSSIBLY_NOT_PASTE = 20 # more than this many events will be assumed to # be a true paste event, i.e. control characters # like '' will be stripped @@ -854,11 +855,19 @@ def send_session_to_external_editor(self, filename=None): _('Session not reevaluated because it was not edited')) return lines = text.split('\n') + if not lines[-1].strip(): + lines.pop() # strip last line if empty + if lines[-1].startswith('### '): + current_line = lines[-1][4:] + else: + current_line = '' from_editor = [line for line in lines if line[:3] != '###'] + source = preprocess('\n'.join(from_editor), self.interp.compile) - self.history = source.split('\n') + lines = source.split('\n') + self.history = lines self.reevaluate(insert_into_history=True) - self.current_line = lines[-1][4:] + self.current_line = current_line self.cursor_offset = len(self.current_line) self.status_bar.message(_('Session edited and reevaluated')) From c7f22dc2a01e9aa770c3fe895d20bd22dc669589 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 13 Dec 2015 14:06:44 -0500 Subject: [PATCH 0651/1650] abort reevuate session if file is blank --- bpython/curtsiesfrontend/repl.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 0082f7ef9..afdc430c2 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -862,6 +862,10 @@ def send_session_to_external_editor(self, filename=None): else: current_line = '' from_editor = [line for line in lines if line[:3] != '###'] + if all(not line.strip() for line in from_editor): + self.status_bar.message( + _('Session not reevaluated because saved file was blank')) + return source = preprocess('\n'.join(from_editor), self.interp.compile) lines = source.split('\n') From 6ef2e279ec4940c3d0d130be8e5b2361a08a9fd7 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 14 Dec 2015 08:19:53 -0500 Subject: [PATCH 0652/1650] ignore documentation test for pypy --- bpython/test/test_repl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index b1d186ee0..ce66d354e 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -222,6 +222,7 @@ def test_issue572(self): self.set_input_line("WonderfulSpam(") self.assertTrue(self.repl.get_args()) + @unittest.skipIf(pypy, 'pypy pydoc doesn\'t have this') def test_issue583(self): self.repl = FakeRepl() self.repl.push("a = 1.2\n", False) From 760d3c7bb81de98da65ba87c2df86c6bc2036a9a Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 6 Dec 2015 15:57:00 -0500 Subject: [PATCH 0653/1650] paginate autocompletion suggestions --- bpython/curtsiesfrontend/replpainter.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 94e813ecf..4d4a529e9 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -48,6 +48,13 @@ def paint_current_line(rows, columns, current_display_line): return fsarray(lines, width=columns) +def paginate(rows, matches, current, words_wide): + if current not in matches: + current = matches[0] + per_page = rows * words_wide + current_page = matches.index(current) // per_page + return matches[per_page * current_page:per_page * (current_page + 1)] + def matches_lines(rows, columns, matches, current, config, format): highlight_color = func_for_letter(config.color_scheme['operator'].lower()) @@ -60,6 +67,8 @@ def matches_lines(rows, columns, matches, current, config, format): if current: current = format(current) + matches = paginate(rows, matches, current, words_wide) + matches_lines = [fmtstr(' ').join(color(m.ljust(max_match_width)) if m != current else highlight_color( @@ -163,12 +172,15 @@ def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, if not (rows and columns): return fsarray(0, 0) width = columns - 4 - lines = ((formatted_argspec(funcprops, arg_pos, width, config) - if funcprops else []) + - (matches_lines(rows, width, matches, match, config, format) - if matches else []) + - (formatted_docstring(docstring, width, config) - if docstring else [])) + from_argspec = (formatted_argspec(funcprops, arg_pos, width, config) + if funcprops else []) + from_doc = (formatted_docstring(docstring, width, config) + if docstring else []) + from_matches = (matches_lines(max(1, rows - len(from_argspec) - 2), + width, matches, match, config, format) + if matches else []) + + lines = from_argspec + from_matches + from_doc def add_border(line): """Add colored borders left and right to a line.""" From f23502da847ef34ad13079ba28f8a5f3666517c7 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 14 Dec 2015 16:50:18 -0500 Subject: [PATCH 0654/1650] fix PEP8 spacing --- bpython/curtsiesfrontend/replpainter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 4d4a529e9..27cdb979e 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -55,6 +55,7 @@ def paginate(rows, matches, current, words_wide): current_page = matches.index(current) // per_page return matches[per_page * current_page:per_page * (current_page + 1)] + def matches_lines(rows, columns, matches, current, config, format): highlight_color = func_for_letter(config.color_scheme['operator'].lower()) From f83e3eb8674a73c38fc95b7e817ba8bf7fca350c Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 13 Feb 2015 19:35:17 -0500 Subject: [PATCH 0655/1650] mitigate jarring scroll up on completion -max completion box size calculated from space on screen -prefer displaying box below, but if scroll require display above -scroll down 10 lines on startup --- bpython/curtsies.py | 8 +++++++- bpython/curtsiesfrontend/repl.py | 26 ++++++++++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 0855eb38b..b4afc1b06 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -158,6 +158,9 @@ def after_suspend(): window.__enter__() interrupting_refresh() + def get_top_usable_line(): + return window.top_usable_row + # global for easy introspection `from bpython.curtsies import repl` global repl with Repl(config=config, @@ -173,9 +176,12 @@ def after_suspend(): interactive=interactive, orig_tcattrs=input_generator.original_stty, on_suspend=on_suspend, - after_suspend=after_suspend) as repl: + after_suspend=after_suspend, + get_top_usable_line=get_top_usable_line) as repl: repl.height, repl.width = window.t.height, window.t.width + repl.request_paint_to_pad_bottom = 10 + def process_event(e): """If None is passed in, just paint the screen""" try: diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index afdc430c2..d1a322c54 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -311,7 +311,8 @@ def __init__(self, interactive=True, orig_tcattrs=None, on_suspend=lambda *args: None, - after_suspend=lambda *args: None): + after_suspend=lambda *args: None, + get_top_usable_line=lambda: 0): """ locals_ is a mapping of locals to pass into the interpreter config is a bpython config.Struct with config attributes @@ -331,6 +332,7 @@ def __init__(self, original terminal state, useful for shelling out with normal terminal on_suspend will be called on sigtstp after_suspend will be called when process foregrounded after suspend + get_top_usable_line returns the top line of the terminal owned """ logger.debug("starting init") @@ -425,6 +427,7 @@ def smarter_request_reload(files_modified=()): self.orig_tcattrs = orig_tcattrs self.on_suspend = on_suspend self.after_suspend = after_suspend + self.get_top_usable_line = get_top_usable_line self.coderunner = CodeRunner(self.interp, self.request_refresh) self.stdout = FakeOutput(self.coderunner, self.send_to_stdout) @@ -434,6 +437,8 @@ def smarter_request_reload(files_modified=()): # next paint should clear screen self.request_paint_to_clear_screen = False + self.request_paint_to_pad_bottom = 0 + # offscreen command yields results different from scrollback bufffer self.inconsistent_history = False @@ -1202,6 +1207,10 @@ def paint(self, about_to_exit=False, user_quit=False): if self.request_paint_to_clear_screen: # or show_status_bar and about_to_exit ? self.request_paint_to_clear_screen = False arr = FSArray(min_height + current_line_start_row, width) + elif self.request_paint_to_pad_bottom: + # min_height - 1 for startup banner with python version + arr = FSArray(min(self.request_paint_to_pad_bottom, min_height - 1), width) + self.request_paint_to_pad_bottom = 0 else: arr = FSArray(0, width) # TODO test case of current line filling up the whole screen (there @@ -1298,7 +1307,8 @@ def move_screen_up(current_line_start_row): if self.list_win_visible and not self.coderunner.running: logger.debug('infobox display code running') visible_space_above = history.height - visible_space_below = min_height - current_line_end_row - 1 + potential_space_below = min_height - current_line_end_row - 1 + visible_space_below = potential_space_below - self.get_top_usable_line() info_max_rows = max(visible_space_above, visible_space_below) infobox = paint.paint_infobox(info_max_rows, @@ -1309,12 +1319,16 @@ def move_screen_up(current_line_start_row): self.current_match, self.docstring, self.config, - self.matches_iter.completer.format if self.matches_iter.completer else None) + self.matches_iter.completer.format + if self.matches_iter.completer + else None) - if visible_space_above >= infobox.height and self.config.curtsies_list_above: - arr[current_line_start_row - infobox.height:current_line_start_row, 0:infobox.width] = infobox - else: + if visible_space_below >= infobox.height or not self.config.curtsies_list_above: + if visible_space_below < infobox.height: + raise ValueError('whoops %r %r' % (visible_space_below, infobox.height)) arr[current_line_end_row + 1:current_line_end_row + 1 + infobox.height, 0:infobox.width] = infobox + else: + arr[current_line_start_row - infobox.height:current_line_start_row, 0:infobox.width] = infobox logger.debug('slamming infobox of shape %r into arr of shape %r', infobox.shape, arr.shape) logger.debug('about to exit: %r', about_to_exit) From a0dd141987626e58a2658861ef38272a10f0b0b6 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 13 Feb 2015 19:46:48 -0500 Subject: [PATCH 0656/1650] allow list_above to be False --- bpython/curtsiesfrontend/repl.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d1a322c54..63d218f87 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1310,7 +1310,11 @@ def move_screen_up(current_line_start_row): potential_space_below = min_height - current_line_end_row - 1 visible_space_below = potential_space_below - self.get_top_usable_line() - info_max_rows = max(visible_space_above, visible_space_below) + if self.config.curtsies_list_above: + info_max_rows = max(visible_space_above, visible_space_below) + else: + minimum_possible_height = 4 + info_max_rows = max(visible_space_below, minimum_possible_height) infobox = paint.paint_infobox(info_max_rows, int(width * self.config.cli_suggestion_width), self.matches_iter.matches, @@ -1324,8 +1328,6 @@ def move_screen_up(current_line_start_row): else None) if visible_space_below >= infobox.height or not self.config.curtsies_list_above: - if visible_space_below < infobox.height: - raise ValueError('whoops %r %r' % (visible_space_below, infobox.height)) arr[current_line_end_row + 1:current_line_end_row + 1 + infobox.height, 0:infobox.width] = infobox else: arr[current_line_start_row - infobox.height:current_line_start_row, 0:infobox.width] = infobox From 01b8f6e4813697b3e66a7c40fa0748b2a6987172 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 18 Feb 2015 16:44:10 -0500 Subject: [PATCH 0657/1650] prevent status bar from flashing at bottom --- bpython/curtsiesfrontend/repl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 63d218f87..254b52641 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1195,7 +1195,8 @@ def paint(self, about_to_exit=False, user_quit=False): self.clean_up_current_line_for_exit() # exception to not changing state! width, min_height = self.width, self.height - show_status_bar = bool(self.status_bar.should_show_message) or self.status_bar.has_focus + show_status_bar = ((bool(self.status_bar.should_show_message) or self.status_bar.has_focus) + and not self.request_paint_to_pad_bottom) if show_status_bar: # because we're going to tack the status bar on at the end, shoot # for an array one less than the height of the screen From d2112a2654a00b9224c24bac8e008a07a4407e2a Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 6 Dec 2015 15:10:32 -0500 Subject: [PATCH 0658/1650] tune size parameters --- bpython/curtsies.py | 2 +- bpython/curtsiesfrontend/repl.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index b4afc1b06..ea69fdeed 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -180,7 +180,7 @@ def get_top_usable_line(): get_top_usable_line=get_top_usable_line) as repl: repl.height, repl.width = window.t.height, window.t.width - repl.request_paint_to_pad_bottom = 10 + repl.request_paint_to_pad_bottom = 6 def process_event(e): """If None is passed in, just paint the screen""" diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 254b52641..65dc0d070 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1314,7 +1314,7 @@ def move_screen_up(current_line_start_row): if self.config.curtsies_list_above: info_max_rows = max(visible_space_above, visible_space_below) else: - minimum_possible_height = 4 + minimum_possible_height = 30 # smallest an over-full completion box info_max_rows = max(visible_space_below, minimum_possible_height) infobox = paint.paint_infobox(info_max_rows, int(width * self.config.cli_suggestion_width), From fbf72d8c5a32c2ed9f6878d981f36a2d2468f0a0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 28 Nov 2015 20:39:56 -0500 Subject: [PATCH 0659/1650] Add strategy for safely evaluating builtins --- bpython/autocomplete.py | 15 +----- bpython/simpleeval.py | 83 +++++++++++++++++++++++++++++++++ bpython/test/test_simpleeval.py | 71 ++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 bpython/simpleeval.py create mode 100644 bpython/test/test_simpleeval.py diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index bc67c7f4d..22d3e9265 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -43,6 +43,7 @@ from bpython.line import LinePart from bpython._py3compat import py3, try_decode from bpython.lazyre import LazyReCompile +from bpython.simpleeval import safe_eval, EvaluationError if not py3: from types import InstanceType, ClassType @@ -648,20 +649,6 @@ def get_completer_bpython(cursor_offset, line, **kwargs): cursor_offset, line, **kwargs) -class EvaluationError(Exception): - """Raised if an exception occurred in safe_eval.""" - - -def safe_eval(expr, namespace): - """Not all that safe, just catches some errors""" - try: - return eval(expr, namespace) - except (NameError, AttributeError, SyntaxError): - # If debugging safe_eval, raise this! - # raise - raise EvaluationError - - def _callable_postfix(value, word): """rlcompleter's _callable_postfix done right.""" with inspection.AttrCleaner(value): diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py new file mode 100644 index 000000000..1ca09418b --- /dev/null +++ b/bpython/simpleeval.py @@ -0,0 +1,83 @@ +# encoding: utf-8 +"""simple evaluation of side-effect free code + +In order to provide fancy completion, some code can be executed safely. + +""" + +from ast import * +from six import string_types + +class EvaluationError(Exception): + """Raised if an exception occurred in safe_eval.""" + + +def safe_eval(expr, namespace): + """Not all that safe, just catches some errors""" + try: + return eval(expr, namespace) + except (NameError, AttributeError, SyntaxError): + # If debugging safe_eval, raise this! + # raise + raise EvaluationError + +def simple_eval(node_or_string, namespace={}): + """ + Safely evaluate an expression node or a string containing a Python + expression. The string or node provided may only consist of: + * the following Python literal structures: strings, numbers, tuples, + lists, dicts, booleans, and None. + * variable names causing lookups in the passed in namespace or builtins + * getitem calls using the [] syntax on objects of the types above + * getitem calls on subclasses of the above types if they do not override + the __getitem__ method and do not override __getattr__ or __getattribute__ + (or maybe we'll try to clean those up?) + + The optional namespace dict-like ought not to cause side effects on lookup + """ + if isinstance(node_or_string, string_types): + node_or_string = parse(node_or_string, mode='eval') + if isinstance(node_or_string, Expression): + node_or_string = node_or_string.body + def _convert(node): + if isinstance(node, Str): + return node.s + elif isinstance(node, Num): + return node.n + elif isinstance(node, Tuple): + return tuple(map(_convert, node.elts)) + elif isinstance(node, List): + return list(map(_convert, node.elts)) + elif isinstance(node, Dict): + return dict((_convert(k), _convert(v)) for k, v + in zip(node.keys, node.values)) + elif isinstance(node, Name): + try: + return namespace[node.id] + except KeyError: + return __builtins__[node.id] + elif isinstance(node, BinOp) and \ + isinstance(node.op, (Add, Sub)) and \ + isinstance(node.right, Num) and \ + isinstance(node.right.n, complex) and \ + isinstance(node.left, Num) and \ + isinstance(node.left.n, (int, long, float)): + left = node.left.n + right = node.right.n + if isinstance(node.op, Add): + return left + right + else: + return left - right + elif isinstance(node, Subscript) and \ + isinstance(node.slice, Index): + obj = _convert(node.value) + index = _convert(node.slice.value) + return safe_getitem(obj, index) + + raise ValueError('malformed string') + return _convert(node_or_string) + +def safe_getitem(obj, index): + if type(obj) in (list, tuple, dict, bytes) + string_types: + return obj[index] + raise ValueError('unsafe to lookup on object of type %s' % (type(obj), )) diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py new file mode 100644 index 000000000..a82cace0c --- /dev/null +++ b/bpython/test/test_simpleeval.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +import ast + +from bpython.simpleeval import simple_eval +from bpython.test import unittest + + +class TestInspection(unittest.TestCase): + def assertMatchesStdlib(self, expr): + self.assertEqual(ast.literal_eval(expr), simple_eval(expr)) + + def test_matches_stdlib(self): + """Should match the stdlib literal_eval if no names or indexing""" + self.assertMatchesStdlib("[1]") + self.assertMatchesStdlib("{(1,): [2,3,{}]}") + + def test_indexing(self): + """Literals can be indexed into""" + self.assertEqual(simple_eval('[1,2][0]'), 1) + self.assertEqual(simple_eval('a', {'a':1}), 1) + + def test_name_lookup(self): + """Names can be lookup up in a namespace""" + self.assertEqual(simple_eval('a', {'a':1}), 1) + self.assertEqual(simple_eval('map'), map) + self.assertEqual(simple_eval('a[b]', {'a':{'c':1}, 'b':'c'}), 1) + + def test_allow_name_lookup(self): + """Names can be lookup up in a namespace""" + self.assertEqual(simple_eval('a', {'a':1}), 1) + + def test_lookup_on_suspicious_types(self): + class FakeDict(object): + pass + + with self.assertRaises(ValueError): + simple_eval('a[1]', {'a': FakeDict()}) + + class TrickyDict(dict): + def __getitem__(self, index): + self.fail("doing key lookup isn't safe") + + with self.assertRaises(ValueError): + simple_eval('a[1]', {'a': TrickyDict()}) + + class SchrodingersDict(dict): + def __getattribute__(inner_self, attr): + self.fail("doing attribute lookup might have side effects") + + with self.assertRaises(ValueError): + simple_eval('a[1]', {'a': SchrodingersDict()}) + + class SchrodingersCatsDict(dict): + def __getattr__(inner_self, attr): + self.fail("doing attribute lookup might have side effects") + + with self.assertRaises(ValueError): + simple_eval('a[1]', {'a': SchrodingersDict()}) + + def test_function_calls_raise(self): + with self.assertRaises(ValueError): + simple_eval('1()') + + def test_nonexistant_names_raise(self): + with self.assertRaises(KeyError): + simple_eval('a') + + +if __name__ == '__main__': + unittest.main() From 504ddd3676891c84621a99820f65f6c37749c24f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 28 Nov 2015 20:46:01 -0500 Subject: [PATCH 0660/1650] Test for more general simple expression finding ast autocompletion will work so well after this --- bpython/line.py | 12 +++++++++ bpython/test/test_line_properties.py | 40 +++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/bpython/line.py b/bpython/line.py index 17599a5e1..c92d979a9 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -263,3 +263,15 @@ def current_indexed_member_access_member(cursor_offset, line): for m in matches: if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: return LinePart(m.start(3), m.end(3), m.group(3)) + +def current_simple_expression(cursor_offset, line): + """The expression attribute lookup being performed on + + e.g. .ba|z + A "simple expression" contains only . lookup and [] indexing.""" + +def current_simple_expression_attribute(cursor_offset, line): + """The attribute being looked up on a simple expression + + e.g. foo[0][1].bar. + A "simple expression" contains only . lookup and [] indexing.""" diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 7ea49f5d8..fe8b4d323 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -7,7 +7,8 @@ current_method_definition_name, current_single_word, \ current_string_literal_attr, current_indexed_member_access_identifier, \ current_indexed_member_access_identifier_with_index, \ - current_indexed_member_access_member + current_indexed_member_access_member, \ + current_simple_expression, current_simple_expression_attribute def cursor(s): @@ -342,5 +343,42 @@ def test_simple(self): self.assertAccess('abc[def].gh |i') self.assertAccess('abc[def]|') +@unittest.skip("TODO") +class TestCurrentSimpleExpression(LineTestCase): + def setUp(self): + self.func = current_simple_expression + + def test_only_dots(self): + self.assertAccess('.attr1|') + self.assertAccess('.|') + self.assertAccess('Object|') + self.assertAccess('Object|.') + self.assertAccess('.|') + self.assertAccess('.attr2|') + self.assertAccess('.att|r1.attr2') + self.assertAccess('stuff[stuff] + {123: 456} + .attr2|') + self.assertAccess('stuff[asd|fg]') + self.assertAccess('stuff[asdf[asd|fg]') + + def test_with_brackets(self): + self.assertAccess('.ba|r') + self.assertAccess('.ba|r baz[qux]xyzzy') + self.assertAccess('foo[.qux|].xyzzy') + self.assertAccess('.xyzzy|') + self.assertAccess('foo[bar[baz].qux].xyzzy, .b|') + self.assertAccess('foo[bar[.|') + self.assertAccess('foo[bar[.|] + 1].qux') + + def test_cases_disallowed_by_simple_eval(self): + # These are allowed for now, but could be changed. + # for example, function calls are not allowed in simple expressions but + # seem like they'd be a pain to weed out so we catch them in the next step.""" + self.assertAccess('foo().bar|') + self.assertAccess('foo[bar(a, b)].baz|') + self.assertAccess('foo(a, b).bar|') + self.assertAccess('<(1 + 1)>.bar|') + self.assertAccess('<(1 + 1 - foo.bar()[1])>.baz|') + + if __name__ == '__main__': unittest.main() From 8254e8704bb394ae8c169d2e1526bab32cb7bcdf Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 30 Nov 2015 13:38:07 -0500 Subject: [PATCH 0661/1650] Test edge case for current word attribute --- bpython/line.py | 34 ++++++++++++++++++++++- bpython/simpleeval.py | 30 ++++++++++++++++++++ bpython/test/test_line_properties.py | 41 ++++------------------------ 3 files changed, 68 insertions(+), 37 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index c92d979a9..5c79dfa85 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -3,10 +3,16 @@ All functions take cursor offset from the beginning of the line and the line of Python code, and return None, or a tuple of the start index, end index, and the word.""" +from __future__ import unicode_literals + +import re from itertools import chain from collections import namedtuple +from pygments.token import Token + from bpython.lazyre import LazyReCompile +from bpython._py3compat import PythonLexer, py3 current_word_re = LazyReCompile(r'[\w_][\w0-9._]*[(]?') LinePart = namedtuple('LinePart', ['start', 'stop', 'word']) @@ -93,7 +99,7 @@ def current_object(cursor_offset, line): return LinePart(start, start+len(s), s) -current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]?') +current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)') def current_object_attribute(cursor_offset, line): @@ -264,14 +270,40 @@ def current_indexed_member_access_member(cursor_offset, line): if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: return LinePart(m.start(3), m.end(3), m.group(3)) +current_simple_expression_re = LazyReCompile( + r'''([a-zA-Z_][\w.]*)\[([a-zA-Z0-9_"']+)\]\.([\w.]*)''') + +def _current_simple_expression(cursor_offset, line): + """ + Returns the current "simple expression" being attribute accessed + + build asts from with increasing numbers of characters. + Find the biggest valid ast. + Once our attribute access is a subtree, stop + + + """ + for i in range(cursor): + pass + + def current_simple_expression(cursor_offset, line): """The expression attribute lookup being performed on e.g. .ba|z A "simple expression" contains only . lookup and [] indexing.""" + def current_simple_expression_attribute(cursor_offset, line): """The attribute being looked up on a simple expression e.g. foo[0][1].bar. A "simple expression" contains only . lookup and [] indexing.""" + + + + + + + + diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 1ca09418b..03ce7aae3 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -81,3 +81,33 @@ def safe_getitem(obj, index): if type(obj) in (list, tuple, dict, bytes) + string_types: return obj[index] raise ValueError('unsafe to lookup on object of type %s' % (type(obj), )) + + +class AttributeSearcher(NodeVisitor): + """Search for a Load of an Attribute at col_offset""" + def visit_attribute(self, node): + print node.attribute + +def _current_simple_expression(cursor_offset, line): + """ + Returns the current "simple expression" being attribute accessed + + build asts from with increasing numbers of characters. + Find the biggest valid ast. + Once our attribute access is a subtree, stop + + + """ + + # in case attribute is blank, e.g. foo.| -> foo.xxx| + temp_line = line[:cursor_offset] + 'xxx' + line[cursor_offset:] + temp_cursor = cursor_offset + 3 + + for i in range(temp_cursor-1, -1, -1): + try: + tree = parse(temp_line[i:temp_cursor]) + except SyntaxError: + return None + + + diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index fe8b4d323..e37fd01e2 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -225,6 +225,11 @@ def test_simple(self): self.assertAccess('stuff[asdf[asd|fg]') self.assertAccess('Object.attr1.<|attr2>') self.assertAccess('Object..attr2') + self.assertAccess('Object..attr2') + + def test_after_dot(self): + self.assertAccess('Object..') + self.assertAccess('Object.attr1.|') class TestCurrentFromImportFrom(LineTestCase): @@ -343,42 +348,6 @@ def test_simple(self): self.assertAccess('abc[def].gh |i') self.assertAccess('abc[def]|') -@unittest.skip("TODO") -class TestCurrentSimpleExpression(LineTestCase): - def setUp(self): - self.func = current_simple_expression - - def test_only_dots(self): - self.assertAccess('.attr1|') - self.assertAccess('.|') - self.assertAccess('Object|') - self.assertAccess('Object|.') - self.assertAccess('.|') - self.assertAccess('.attr2|') - self.assertAccess('.att|r1.attr2') - self.assertAccess('stuff[stuff] + {123: 456} + .attr2|') - self.assertAccess('stuff[asd|fg]') - self.assertAccess('stuff[asdf[asd|fg]') - - def test_with_brackets(self): - self.assertAccess('.ba|r') - self.assertAccess('.ba|r baz[qux]xyzzy') - self.assertAccess('foo[.qux|].xyzzy') - self.assertAccess('.xyzzy|') - self.assertAccess('foo[bar[baz].qux].xyzzy, .b|') - self.assertAccess('foo[bar[.|') - self.assertAccess('foo[bar[.|] + 1].qux') - - def test_cases_disallowed_by_simple_eval(self): - # These are allowed for now, but could be changed. - # for example, function calls are not allowed in simple expressions but - # seem like they'd be a pain to weed out so we catch them in the next step.""" - self.assertAccess('foo().bar|') - self.assertAccess('foo[bar(a, b)].baz|') - self.assertAccess('foo(a, b).bar|') - self.assertAccess('<(1 + 1)>.bar|') - self.assertAccess('<(1 + 1 - foo.bar()[1])>.baz|') - if __name__ == '__main__': unittest.main() From 488d5e6ac974dbd96abd71583902e74e422c16eb Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 30 Nov 2015 12:30:44 -0500 Subject: [PATCH 0662/1650] Evaluation of expression before dot --- bpython/line.py | 50 +++++--------------- bpython/simpleeval.py | 70 ++++++++++++++++++++-------- bpython/test/test_line_properties.py | 33 +++++++++++-- bpython/test/test_simpleeval.py | 41 +++++++++++++++- 4 files changed, 129 insertions(+), 65 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 5c79dfa85..1a79b3028 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -5,14 +5,10 @@ word.""" from __future__ import unicode_literals -import re - from itertools import chain from collections import namedtuple -from pygments.token import Token from bpython.lazyre import LazyReCompile -from bpython._py3compat import PythonLexer, py3 current_word_re = LazyReCompile(r'[\w_][\w0-9._]*[(]?') LinePart = namedtuple('LinePart', ['start', 'stop', 'word']) @@ -99,11 +95,12 @@ def current_object(cursor_offset, line): return LinePart(start, start+len(s), s) -current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)') +current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]?') def current_object_attribute(cursor_offset, line): """If in attribute completion, the attribute being completed""" + #TODO replace with more general current_expression_attribute match = current_word(cursor_offset, line) if match is None: return None @@ -270,40 +267,15 @@ def current_indexed_member_access_member(cursor_offset, line): if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: return LinePart(m.start(3), m.end(3), m.group(3)) -current_simple_expression_re = LazyReCompile( - r'''([a-zA-Z_][\w.]*)\[([a-zA-Z0-9_"']+)\]\.([\w.]*)''') - -def _current_simple_expression(cursor_offset, line): - """ - Returns the current "simple expression" being attribute accessed - - build asts from with increasing numbers of characters. - Find the biggest valid ast. - Once our attribute access is a subtree, stop - - - """ - for i in range(cursor): - pass - - -def current_simple_expression(cursor_offset, line): - """The expression attribute lookup being performed on - - e.g. .ba|z - A "simple expression" contains only . lookup and [] indexing.""" - - -def current_simple_expression_attribute(cursor_offset, line): - """The attribute being looked up on a simple expression - - e.g. foo[0][1].bar. - A "simple expression" contains only . lookup and [] indexing.""" - - - - - +current_expression_attribute_re = LazyReCompile(r'[.]\s*((?:[\w_][\w0-9_]*)|(?:))') +def current_expression_attribute(cursor_offset, line): + """If after a dot, the attribute being completed""" + #TODO replace with more general current_expression_attribute + matches = current_expression_attribute_re.finditer(line) + for m in matches: + if (m.start(1) <= cursor_offset and m.end(1) >= cursor_offset): + return LinePart(m.start(1), m.end(1), m.group(1)) + return None diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 03ce7aae3..93a222cb7 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -8,6 +8,8 @@ from ast import * from six import string_types +from bpython import line as line_properties + class EvaluationError(Exception): """Raised if an exception occurred in safe_eval.""" @@ -21,7 +23,7 @@ def safe_eval(expr, namespace): # raise raise EvaluationError -def simple_eval(node_or_string, namespace={}): +def simple_eval(node_or_string, namespace=None): """ Safely evaluate an expression node or a string containing a Python expression. The string or node provided may only consist of: @@ -35,6 +37,8 @@ def simple_eval(node_or_string, namespace={}): The optional namespace dict-like ought not to cause side effects on lookup """ + if namespace is None: + namespace = {} if isinstance(node_or_string, string_types): node_or_string = parse(node_or_string, mode='eval') if isinstance(node_or_string, Expression): @@ -77,37 +81,65 @@ def _convert(node): raise ValueError('malformed string') return _convert(node_or_string) + def safe_getitem(obj, index): if type(obj) in (list, tuple, dict, bytes) + string_types: return obj[index] raise ValueError('unsafe to lookup on object of type %s' % (type(obj), )) -class AttributeSearcher(NodeVisitor): - """Search for a Load of an Attribute at col_offset""" - def visit_attribute(self, node): - print node.attribute - -def _current_simple_expression(cursor_offset, line): +def find_attribute_with_name(node, name): + """Based on ast.NodeVisitor""" + if isinstance(node, Attribute) and node.attr == name: + return node + for field, value in iter_fields(node): + if isinstance(value, list): + for item in value: + if isinstance(item, AST): + r = find_attribute_with_name(item, name) + if r: + return r + elif isinstance(value, AST): + r = find_attribute_with_name(value, name) + if r: + return r + + +def evaluate_current_expression(cursor_offset, line, namespace={}): """ - Returns the current "simple expression" being attribute accessed + Return evaluted expression to the right of the dot of current attribute. build asts from with increasing numbers of characters. Find the biggest valid ast. Once our attribute access is a subtree, stop - - """ # in case attribute is blank, e.g. foo.| -> foo.xxx| temp_line = line[:cursor_offset] + 'xxx' + line[cursor_offset:] temp_cursor = cursor_offset + 3 - - for i in range(temp_cursor-1, -1, -1): - try: - tree = parse(temp_line[i:temp_cursor]) - except SyntaxError: - return None - - - + temp_attribute = line_properties.current_expression_attribute( + temp_cursor, temp_line) + if temp_attribute is None: + raise EvaluationError("No current attribute") + attr_before_cursor = temp_line[temp_attribute.start:temp_cursor] + + def parse_trees(cursor_offset, line): + for i in range(cursor_offset-1, -1, -1): + try: + ast = parse(line[i:cursor_offset]) + yield ast + except SyntaxError: + continue + + largest_ast = None + for tree in parse_trees(temp_cursor, temp_line): + attribute_access = find_attribute_with_name(tree, attr_before_cursor) + if attribute_access: + largest_ast = attribute_access.value + + if largest_ast is None: + raise EvaluationError("Corresponding ASTs to right of cursor are invalid") + try: + return simple_eval(largest_ast, namespace) + except (ValueError, KeyError, IndexError): + raise EvaluationError("Could not safely evaluate") diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index e37fd01e2..a38a76426 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -8,7 +8,7 @@ current_string_literal_attr, current_indexed_member_access_identifier, \ current_indexed_member_access_identifier_with_index, \ current_indexed_member_access_member, \ - current_simple_expression, current_simple_expression_attribute + current_expression_attribute def cursor(s): @@ -227,10 +227,6 @@ def test_simple(self): self.assertAccess('Object..attr2') self.assertAccess('Object..attr2') - def test_after_dot(self): - self.assertAccess('Object..') - self.assertAccess('Object.attr1.|') - class TestCurrentFromImportFrom(LineTestCase): def setUp(self): @@ -348,6 +344,33 @@ def test_simple(self): self.assertAccess('abc[def].gh |i') self.assertAccess('abc[def]|') +class TestCurrentExpressionAttribute(LineTestCase): + def setUp(self): + self.func = current_expression_attribute + + def test_simple(self): + self.assertAccess('Object..') + self.assertAccess('Object.<|attr1>.') + self.assertAccess('Object.(|)') + self.assertAccess('Object.another.(|)') + self.assertAccess('asdf asdf asdf.(abc|)') + + def test_without_dot(self): + self.assertAccess('Object|') + self.assertAccess('Object|.') + self.assertAccess('|Object.') + + def test_with_whitespace(self): + self.assertAccess('Object. ') + self.assertAccess('Object .') + self.assertAccess('Object . ') + self.assertAccess('Object .asdf attr|') + self.assertAccess('Object . attr') + self.assertAccess('Object. asdf attr|') + self.assertAccess('Object. attr') + self.assertAccess('Object . asdf attr|') + self.assertAccess('Object . attr') + if __name__ == '__main__': unittest.main() diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index a82cace0c..13c6aef11 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -2,11 +2,13 @@ import ast -from bpython.simpleeval import simple_eval +from bpython.simpleeval import (simple_eval, + evaluate_current_expression, + EvaluationError) from bpython.test import unittest -class TestInspection(unittest.TestCase): +class TestSimpleEval(unittest.TestCase): def assertMatchesStdlib(self, expr): self.assertEqual(ast.literal_eval(expr), simple_eval(expr)) @@ -66,6 +68,41 @@ def test_nonexistant_names_raise(self): with self.assertRaises(KeyError): simple_eval('a') +class TestEvaluateCurrentExpression(unittest.TestCase): + + def assertEvaled(self, line, value, ns=None): + assert line.count('|') == 1 + cursor_offset = line.find('|') + line = line.replace('|', '') + self.assertEqual(evaluate_current_expression(cursor_offset, line, ns), + value) + + def assertCannotEval(self, line, ns=None): + assert line.count('|') == 1 + cursor_offset = line.find('|') + line = line.replace('|', '') + with self.assertRaises(EvaluationError): + evaluate_current_expression(cursor_offset, line, ns) + + def test_simple(self): + self.assertEvaled('[1].a|bc', [1]) + self.assertEvaled('[1].abc|', [1]) + self.assertEvaled('[1].|abc', [1]) + self.assertEvaled('[1]. |abc', [1]) + self.assertEvaled('[1] .|abc', [1]) + self.assertCannotEval('[1].abc |', [1]) + self.assertCannotEval('[1]. abc |', [1]) + self.assertCannotEval('[2][1].a|bc', [1]) + + def test_nonsense(self): + self.assertEvaled('!@#$ [1].a|bc', [1]) + self.assertEvaled('--- [2][0].a|bc', 2) + self.assertCannotEval('"asdf".centered()[1].a|bc') + self.assertEvaled('"asdf"[1].a|bc', 's') + + def test_with_namespace(self): + self.assertEvaled('a[1].a|bc', 'd', {'a':'adsf'}) + self.assertCannotEval('a[1].a|bc', {}) if __name__ == '__main__': unittest.main() From 51ecedb596f801f44dd4a3657274c59cd4b16c1f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 30 Nov 2015 13:20:11 -0500 Subject: [PATCH 0663/1650] Add autocompletion of attributes of simple expressions there's now duplication in completers --- bpython/autocomplete.py | 43 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 22d3e9265..f93c89c4b 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -43,7 +43,8 @@ from bpython.line import LinePart from bpython._py3compat import py3, try_decode from bpython.lazyre import LazyReCompile -from bpython.simpleeval import safe_eval, EvaluationError +from bpython.simpleeval import (safe_eval, evaluate_current_expression, + EvaluationError) if not py3: from types import InstanceType, ClassType @@ -241,7 +242,7 @@ def matches(self, cursor_offset, line, **kwargs): if r is None: return None - if locals_ is None: + if locals_ is None: # TODO add a note about why locals_ = __main__.__dict__ assert '.' in r.word @@ -524,6 +525,41 @@ def locate(self, current_offset, line): return lineparts.current_string_literal_attr(current_offset, line) +class ExpressionAttributeCompletion(AttrCompletion): + # could replace ArrayItemMember completion and attr completion + # as a more general case + def locate(self, current_offset, line): + return lineparts.current_expression_attribute(current_offset, line) + + def matches(self, cursor_offset, line, **kwargs): + if 'locals_' not in kwargs: + return None + locals_ = kwargs['locals_'] + + if locals_ is None: + locals_ = __main__.__dict__ + + attr = self.locate(cursor_offset, line) + + try: + obj = evaluate_current_expression(cursor_offset, line, locals_) + except EvaluationError: + return set() + with inspection.AttrCleaner(obj): + # strips leading dot + matches = [m[1:] for m in self.attr_lookup(obj, '', attr.word)] + + if attr.word.startswith('__'): + pass + elif attr.word.startswith('_'): + matches = set(match for match in matches + if not match.startswith('__')) + else: + matches = set(match for match in matches + if not match.split('.')[-1].startswith('_')) + return matches + + try: import jedi except ImportError: @@ -638,8 +674,9 @@ def get_default_completer(mode=SIMPLE): CumulativeCompleter((GlobalCompletion(mode=mode), ParameterNameCompletion(mode=mode)), mode=mode), - ArrayItemMembersCompletion(mode=mode), AttrCompletion(mode=mode), + ExpressionAttributeCompletion(mode=mode), + ArrayItemMembersCompletion(mode=mode), ) From ce0bd0a3fa60e7b3398f60439d71efdf319a5bf5 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 30 Nov 2015 13:47:46 -0500 Subject: [PATCH 0664/1650] Remove ArrayItemMembersCompletion functionality taken over by more general ExpressionAttributeCompletion --- bpython/autocomplete.py | 61 +------------------------------ bpython/test/test_autocomplete.py | 12 +++--- 2 files changed, 7 insertions(+), 66 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index f93c89c4b..22b425c7b 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -340,63 +340,6 @@ def list_attributes(self, obj): return dir(obj) -class ArrayItemMembersCompletion(BaseCompletionType): - - def __init__(self, shown_before_tab=True, mode=SIMPLE): - self._shown_before_tab = shown_before_tab - self.completer = AttrCompletion(mode=mode) - - def matches(self, cursor_offset, line, **kwargs): - if 'locals_' not in kwargs: - return None - locals_ = kwargs['locals_'] - - full = self.locate(cursor_offset, line) - if full is None: - return None - - arr = lineparts.current_indexed_member_access_identifier( - cursor_offset, line) - index = lineparts.current_indexed_member_access_identifier_with_index( - cursor_offset, line) - member = lineparts.current_indexed_member_access_member( - cursor_offset, line) - - try: - obj = safe_eval(arr.word, locals_) - except EvaluationError: - return None - if type(obj) not in (list, tuple) + string_types: - # then is may be unsafe to do attribute lookup on it - return None - - try: - locals_['temp_val_from_array'] = safe_eval(index.word, locals_) - except (EvaluationError, IndexError): - return None - - temp_line = line.replace(index.word, 'temp_val_from_array.') - - matches = self.completer.matches(len(temp_line), temp_line, **kwargs) - if matches is None: - return None - - matches_with_correct_name = \ - set(match.replace('temp_val_from_array.', index.word+'.') - for match in matches if match[20:].startswith(member.word)) - - del locals_['temp_val_from_array'] - - return matches_with_correct_name - - def locate(self, current_offset, line): - a = lineparts.current_indexed_member_access(current_offset, line) - return a - - def format(self, match): - return after_last_dot(match) - - class DictKeyCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): @@ -526,8 +469,7 @@ def locate(self, current_offset, line): class ExpressionAttributeCompletion(AttrCompletion): - # could replace ArrayItemMember completion and attr completion - # as a more general case + # could replace attr completion as a more general case with some work def locate(self, current_offset, line): return lineparts.current_expression_attribute(current_offset, line) @@ -676,7 +618,6 @@ def get_default_completer(mode=SIMPLE): mode=mode), AttrCompletion(mode=mode), ExpressionAttributeCompletion(mode=mode), - ArrayItemMembersCompletion(mode=mode), ) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index c713d363c..bd1051747 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -268,20 +268,20 @@ def __getattr__(self, attr): self.com.matches(4, 'a.__', locals_=locals_)) -class TestArrayItemCompletion(unittest.TestCase): +class TestExpressionAttributeCompletion(unittest.TestCase): @classmethod def setUpClass(cls): - cls.com = autocomplete.ArrayItemMembersCompletion() + cls.com = autocomplete.ExpressionAttributeCompletion() def test_att_matches_found_on_instance(self): self.assertSetEqual(self.com.matches(5, 'a[0].', locals_={'a': [Foo()]}), - set(['a[0].method', 'a[0].a', 'a[0].b'])) + set(['method', 'a', 'b'])) @skip_old_style def test_att_matches_found_on_old_style_instance(self): self.assertSetEqual(self.com.matches(5, 'a[0].', locals_={'a': [OldStyleFoo()]}), - set(['a[0].method', 'a[0].a', 'a[0].b'])) + set(['method', 'a', 'b'])) def test_other_getitem_methods_not_called(self): class FakeList(object): @@ -293,14 +293,14 @@ def __getitem__(inner_self, i): def test_tuples_complete(self): self.assertSetEqual(self.com.matches(5, 'a[0].', locals_={'a': (Foo(),)}), - set(['a[0].method', 'a[0].a', 'a[0].b'])) + set(['method', 'a', 'b'])) @unittest.skip('TODO, subclasses do not complete yet') def test_list_subclasses_complete(self): class ListSubclass(list): pass self.assertSetEqual(self.com.matches(5, 'a[0].', locals_={'a': ListSubclass([Foo()])}), - set(['a[0].method', 'a[0].a', 'a[0].b'])) + set(['method', 'a', 'b'])) def test_getitem_not_called_in_list_subclasses_overriding_getitem(self): class FakeList(list): From b0951cd2a70a0a72f681defdbd82d4d37a4c5739 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 30 Nov 2015 13:55:57 -0500 Subject: [PATCH 0665/1650] Test attribute completion on other expressions --- bpython/test/test_autocomplete.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index bd1051747..119fdd3dd 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -309,6 +309,16 @@ def __getitem__(inner_self, i): self.com.matches(5, 'a[0].', locals_={'a': FakeList()}) + def test_literals_complete(self): + self.assertSetEqual(self.com.matches(10, '[a][0][0].', + locals_={'a': (Foo(),)}), + set(['method', 'a', 'b'])) + + def test_dictionaries_complete(self): + self.assertSetEqual(self.com.matches(7, 'a["b"].', + locals_={'a': {'b': Foo()}}), + set(['method', 'a', 'b'])) + class TestMagicMethodCompletion(unittest.TestCase): From c16c2f28f7836f1b311f30dd92016a5b6550bf22 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 1 Dec 2015 10:39:46 -0500 Subject: [PATCH 0666/1650] remove ast star import, fix builtin import --- bpython/simpleeval.py | 51 +++++++++++++++++---------------- bpython/test/test_simpleeval.py | 2 +- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 93a222cb7..7d0566249 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -5,8 +5,9 @@ """ -from ast import * +import ast from six import string_types +from six.moves import builtins from bpython import line as line_properties @@ -23,6 +24,7 @@ def safe_eval(expr, namespace): # raise raise EvaluationError +# taken from Python 2 stdlib ast.literal_eval def simple_eval(node_or_string, namespace=None): """ Safely evaluate an expression node or a string containing a Python @@ -37,43 +39,44 @@ def simple_eval(node_or_string, namespace=None): The optional namespace dict-like ought not to cause side effects on lookup """ + # Based heavily on stdlib ast.literal_eval if namespace is None: namespace = {} if isinstance(node_or_string, string_types): - node_or_string = parse(node_or_string, mode='eval') - if isinstance(node_or_string, Expression): + node_or_string = ast.parse(node_or_string, mode='eval') + if isinstance(node_or_string, ast.Expression): node_or_string = node_or_string.body def _convert(node): - if isinstance(node, Str): + if isinstance(node, ast.Str): return node.s - elif isinstance(node, Num): + elif isinstance(node, ast.Num): return node.n - elif isinstance(node, Tuple): + elif isinstance(node, ast.Tuple): return tuple(map(_convert, node.elts)) - elif isinstance(node, List): + elif isinstance(node, ast.List): return list(map(_convert, node.elts)) - elif isinstance(node, Dict): + elif isinstance(node, ast.Dict): return dict((_convert(k), _convert(v)) for k, v in zip(node.keys, node.values)) - elif isinstance(node, Name): + elif isinstance(node, ast.Name): try: return namespace[node.id] except KeyError: - return __builtins__[node.id] - elif isinstance(node, BinOp) and \ - isinstance(node.op, (Add, Sub)) and \ - isinstance(node.right, Num) and \ + return getattr(builtins, node.id) + elif isinstance(node, ast.BinOp) and \ + isinstance(node.op, (ast.Add, ast.Sub)) and \ + isinstance(node.right, ast.Num) and \ isinstance(node.right.n, complex) and \ - isinstance(node.left, Num) and \ + isinstance(node.left, ast.Num) and \ isinstance(node.left.n, (int, long, float)): left = node.left.n right = node.right.n - if isinstance(node.op, Add): + if isinstance(node.op, ast.Add): return left + right else: return left - right - elif isinstance(node, Subscript) and \ - isinstance(node.slice, Index): + elif isinstance(node, ast.Subscript) and \ + isinstance(node.slice, ast.Index): obj = _convert(node.value) index = _convert(node.slice.value) return safe_getitem(obj, index) @@ -90,16 +93,16 @@ def safe_getitem(obj, index): def find_attribute_with_name(node, name): """Based on ast.NodeVisitor""" - if isinstance(node, Attribute) and node.attr == name: + if isinstance(node, ast.Attribute) and node.attr == name: return node - for field, value in iter_fields(node): + for field, value in ast.iter_fields(node): if isinstance(value, list): for item in value: - if isinstance(item, AST): + if isinstance(item, ast.AST): r = find_attribute_with_name(item, name) if r: return r - elif isinstance(value, AST): + elif isinstance(value, ast.AST): r = find_attribute_with_name(value, name) if r: return r @@ -126,8 +129,8 @@ def evaluate_current_expression(cursor_offset, line, namespace={}): def parse_trees(cursor_offset, line): for i in range(cursor_offset-1, -1, -1): try: - ast = parse(line[i:cursor_offset]) - yield ast + tree = ast.parse(line[i:cursor_offset]) + yield tree except SyntaxError: continue @@ -141,5 +144,5 @@ def parse_trees(cursor_offset, line): raise EvaluationError("Corresponding ASTs to right of cursor are invalid") try: return simple_eval(largest_ast, namespace) - except (ValueError, KeyError, IndexError): + except (ValueError, KeyError, IndexError, AttributeError): raise EvaluationError("Could not safely evaluate") diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 13c6aef11..e591d11a1 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -65,7 +65,7 @@ def test_function_calls_raise(self): simple_eval('1()') def test_nonexistant_names_raise(self): - with self.assertRaises(KeyError): + with self.assertRaises((KeyError, AttributeError)): simple_eval('a') class TestEvaluateCurrentExpression(unittest.TestCase): From f7165d49e2c1f334d26049a275a8fc4b2860f122 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 1 Dec 2015 10:42:25 -0500 Subject: [PATCH 0667/1650] fix docstring of simple_eval --- bpython/simpleeval.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 7d0566249..89feb08ef 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -30,12 +30,9 @@ def simple_eval(node_or_string, namespace=None): Safely evaluate an expression node or a string containing a Python expression. The string or node provided may only consist of: * the following Python literal structures: strings, numbers, tuples, - lists, dicts, booleans, and None. + lists, and dicts * variable names causing lookups in the passed in namespace or builtins * getitem calls using the [] syntax on objects of the types above - * getitem calls on subclasses of the above types if they do not override - the __getitem__ method and do not override __getattr__ or __getattribute__ - (or maybe we'll try to clean those up?) The optional namespace dict-like ought not to cause side effects on lookup """ From 9a25c6915182093d5685e7fbebb7649467cb23e3 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 1 Dec 2015 10:45:32 -0500 Subject: [PATCH 0668/1650] remove unused code --- bpython/line.py | 37 ---------------------- bpython/test/test_line_properties.py | 46 +++++----------------------- 2 files changed, 7 insertions(+), 76 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 1a79b3028..3874e654a 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -231,43 +231,6 @@ def current_string_literal_attr(cursor_offset, line): return None -current_indexed_member_re = LazyReCompile( - r'''([a-zA-Z_][\w.]*)\[([a-zA-Z0-9_"']+)\]\.([\w.]*)''') - - -def current_indexed_member_access(cursor_offset, line): - """An identifier being indexed and member accessed""" - matches = current_indexed_member_re.finditer(line) - for m in matches: - if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: - return LinePart(m.start(1), m.end(3), m.group()) - - -def current_indexed_member_access_identifier(cursor_offset, line): - """An identifier being indexed, e.g. foo in foo[1].bar""" - matches = current_indexed_member_re.finditer(line) - for m in matches: - if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: - return LinePart(m.start(1), m.end(1), m.group(1)) - - -def current_indexed_member_access_identifier_with_index(cursor_offset, line): - """An identifier being indexed with the index, e.g. foo[1] in foo[1].bar""" - matches = current_indexed_member_re.finditer(line) - for m in matches: - if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: - return LinePart(m.start(1), m.end(2)+1, - "%s[%s]" % (m.group(1), m.group(2))) - - -def current_indexed_member_access_member(cursor_offset, line): - """The member name of an indexed object, e.g. bar in foo[1].bar""" - matches = current_indexed_member_re.finditer(line) - for m in matches: - if m.start(3) <= cursor_offset and m.end(3) >= cursor_offset: - return LinePart(m.start(3), m.end(3), m.group(3)) - - current_expression_attribute_re = LazyReCompile(r'[.]\s*((?:[\w_][\w0-9_]*)|(?:))') diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index a38a76426..d44cf17d9 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -5,10 +5,7 @@ current_string, current_object, current_object_attribute, \ current_from_import_from, current_from_import_import, current_import, \ current_method_definition_name, current_single_word, \ - current_string_literal_attr, current_indexed_member_access_identifier, \ - current_indexed_member_access_identifier_with_index, \ - current_indexed_member_access_member, \ - current_expression_attribute + current_string_literal_attr, current_expression_attribute def cursor(s): @@ -225,7 +222,6 @@ def test_simple(self): self.assertAccess('stuff[asdf[asd|fg]') self.assertAccess('Object.attr1.<|attr2>') self.assertAccess('Object..attr2') - self.assertAccess('Object..attr2') class TestCurrentFromImportFrom(LineTestCase): @@ -309,40 +305,6 @@ def test_simple(self): self.assertAccess('"hey".asdf d|') self.assertAccess('"hey".<|>') -class TestCurrentIndexedMemberAccessIdentifier(LineTestCase): - def setUp(self): - self.func = current_indexed_member_access_identifier - - def test_simple(self): - self.assertAccess('[def].ghi|') - self.assertAccess('[def].|ghi') - self.assertAccess('[def].gh|i') - self.assertAccess('abc[def].gh |i') - self.assertAccess('abc[def]|') - - -class TestCurrentIndexedMemberAccessIdentifierWithIndex(LineTestCase): - def setUp(self): - self.func = current_indexed_member_access_identifier_with_index - - def test_simple(self): - self.assertAccess('.ghi|') - self.assertAccess('.|ghi') - self.assertAccess('.gh|i') - self.assertAccess('abc[def].gh |i') - self.assertAccess('abc[def]|') - - -class TestCurrentIndexedMemberAccessMember(LineTestCase): - def setUp(self): - self.func = current_indexed_member_access_member - - def test_simple(self): - self.assertAccess('abc[def].') - self.assertAccess('abc[def].<|ghi>') - self.assertAccess('abc[def].') - self.assertAccess('abc[def].gh |i') - self.assertAccess('abc[def]|') class TestCurrentExpressionAttribute(LineTestCase): def setUp(self): @@ -371,6 +333,12 @@ def test_with_whitespace(self): self.assertAccess('Object . asdf attr|') self.assertAccess('Object . attr') + def test_indexing(self): + self.assertAccess('abc[def].') + self.assertAccess('abc[def].<|ghi>') + self.assertAccess('abc[def].') + self.assertAccess('abc[def].gh |i') + self.assertAccess('abc[def]|') if __name__ == '__main__': unittest.main() From d36c30dceeba01f2f28e690711a0c16524696ade Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 1 Dec 2015 10:52:30 -0500 Subject: [PATCH 0669/1650] Remove StringLiteralAttrCompletion --- bpython/autocomplete.py | 22 ---------------------- bpython/line.py | 15 --------------- bpython/test/test_line_properties.py | 24 ++++++++++-------------- 3 files changed, 10 insertions(+), 51 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 22b425c7b..7855c3225 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -447,27 +447,6 @@ def locate(self, current_offset, line): return lineparts.current_word(current_offset, line) -class StringLiteralAttrCompletion(BaseCompletionType): - - def matches(self, cursor_offset, line, **kwargs): - r = self.locate(cursor_offset, line) - if r is None: - return None - - attrs = dir('') - if not py3: - # decode attributes - attrs = (att.decode('ascii') for att in attrs) - - matches = set(att for att in attrs if att.startswith(r.word)) - if not r.word.startswith('_'): - return set(match for match in matches if not match.startswith('_')) - return matches - - def locate(self, current_offset, line): - return lineparts.current_string_literal_attr(current_offset, line) - - class ExpressionAttributeCompletion(AttrCompletion): # could replace attr completion as a more general case with some work def locate(self, current_offset, line): @@ -608,7 +587,6 @@ def get_completer(completers, cursor_offset, line, **kwargs): def get_default_completer(mode=SIMPLE): return ( DictKeyCompletion(mode=mode), - StringLiteralAttrCompletion(mode=mode), ImportCompletion(mode=mode), FilenameCompletion(mode=mode), MagicMethodCompletion(mode=mode), diff --git a/bpython/line.py b/bpython/line.py index 3874e654a..996f850db 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -216,21 +216,6 @@ def current_dotted_attribute(cursor_offset, line): return LinePart(start, end, word) -current_string_literal_attr_re = LazyReCompile( - "('''" + - r'''|"""|'|")''' + - r'''((?:(?=([^"'\\]+|\\.|(?!\1)["']))\3)*)\1[.]([a-zA-Z_]?[\w]*)''') - - -def current_string_literal_attr(cursor_offset, line): - """The attribute following a string literal""" - matches = current_string_literal_attr_re.finditer(line) - for m in matches: - if m.start(4) <= cursor_offset and m.end(4) >= cursor_offset: - return LinePart(m.start(4), m.end(4), m.group(4)) - return None - - current_expression_attribute_re = LazyReCompile(r'[.]\s*((?:[\w_][\w0-9_]*)|(?:))') diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index d44cf17d9..0d9f611b7 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -5,7 +5,7 @@ current_string, current_object, current_object_attribute, \ current_from_import_from, current_from_import_import, current_import, \ current_method_definition_name, current_single_word, \ - current_string_literal_attr, current_expression_attribute + current_expression_attribute def cursor(s): @@ -293,19 +293,6 @@ def test_simple(self): self.assertAccess(' ') -class TestCurrentStringLiteral(LineTestCase): - def setUp(self): - self.func = current_string_literal_attr - - def test_simple(self): - self.assertAccess('"hey".') - self.assertAccess('"hey"|') - self.assertAccess('"hey"|.a') - self.assertAccess('"hey".') - self.assertAccess('"hey".asdf d|') - self.assertAccess('"hey".<|>') - - class TestCurrentExpressionAttribute(LineTestCase): def setUp(self): self.func = current_expression_attribute @@ -340,5 +327,14 @@ def test_indexing(self): self.assertAccess('abc[def].gh |i') self.assertAccess('abc[def]|') + def test_strings(self): + self.assertAccess('"hey".') + self.assertAccess('"hey"|') + self.assertAccess('"hey"|.a') + self.assertAccess('"hey".') + self.assertAccess('"hey".asdf d|') + self.assertAccess('"hey".<|>') + + if __name__ == '__main__': unittest.main() From b093ced3375691188c208a1109395db4a32ce951 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 2 Dec 2015 09:43:04 -0500 Subject: [PATCH 0670/1650] Single-source Py2/3 simple_eval --- bpython/simpleeval.py | 57 +++++++++++++++++++++++++-------- bpython/test/test_simpleeval.py | 2 +- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 89feb08ef..650977aa7 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -8,8 +8,10 @@ import ast from six import string_types from six.moves import builtins +from numbers import Number from bpython import line as line_properties +from bpython._py3compat import py3 class EvaluationError(Exception): """Raised if an exception occurred in safe_eval.""" @@ -24,7 +26,7 @@ def safe_eval(expr, namespace): # raise raise EvaluationError -# taken from Python 2 stdlib ast.literal_eval + def simple_eval(node_or_string, namespace=None): """ Safely evaluate an expression node or a string containing a Python @@ -34,6 +36,9 @@ def simple_eval(node_or_string, namespace=None): * variable names causing lookups in the passed in namespace or builtins * getitem calls using the [] syntax on objects of the types above + Like the Python 3 (and unlike the Python 2) literal_eval, unary and binary + + and - operations are allowed on all builtin numeric types. + The optional namespace dict-like ought not to cause side effects on lookup """ # Based heavily on stdlib ast.literal_eval @@ -43,8 +48,13 @@ def simple_eval(node_or_string, namespace=None): node_or_string = ast.parse(node_or_string, mode='eval') if isinstance(node_or_string, ast.Expression): node_or_string = node_or_string.body + + string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) + name_type_nodes = (ast.Name, ast.NameConstant) if py3 else (ast.Name,) + numeric_types = (int, float, complex) + (() if py3 else (long,)) + def _convert(node): - if isinstance(node, ast.Str): + if isinstance(node, string_type_nodes): return node.s elif isinstance(node, ast.Num): return node.n @@ -55,23 +65,41 @@ def _convert(node): elif isinstance(node, ast.Dict): return dict((_convert(k), _convert(v)) for k, v in zip(node.keys, node.values)) - elif isinstance(node, ast.Name): + + # this is a deviation from literal_eval: we allow non-literals + elif isinstance(node, name_type_nodes): try: return namespace[node.id] except KeyError: - return getattr(builtins, node.id) + try: + return getattr(builtins, node.id) + except AttributeError: + raise EvaluationError("can't lookup %s" % node.id) + + # unary + and - are allowed on any type + elif isinstance(node, ast.UnaryOp) and \ + isinstance(node.op, (ast.UAdd, ast.USub)): + # ast.literal_eval does ast typechecks here, we use type checks + operand = _convert(node.operand) + if not type(operand) in numeric_types: + raise ValueError("unary + and - only allowed on builtin nums") + if isinstance(node.op, ast.UAdd): + return + operand + else: + return - operand elif isinstance(node, ast.BinOp) and \ - isinstance(node.op, (ast.Add, ast.Sub)) and \ - isinstance(node.right, ast.Num) and \ - isinstance(node.right.n, complex) and \ - isinstance(node.left, ast.Num) and \ - isinstance(node.left.n, (int, long, float)): - left = node.left.n - right = node.right.n + isinstance(node.op, (ast.Add, ast.Sub)): + # ast.literal_eval does ast typechecks here, we use type checks + left = _convert(node.left) + right = _convert(node.right) + if not (type(left) in numeric_types and type(right) in numeric_types): + raise ValueError("binary + and - only allowed on builtin nums") if isinstance(node.op, ast.Add): return left + right else: return left - right + + # this is a deviation from literal_eval: we allow indexing elif isinstance(node, ast.Subscript) and \ isinstance(node.slice, ast.Index): obj = _convert(node.value) @@ -84,7 +112,10 @@ def _convert(node): def safe_getitem(obj, index): if type(obj) in (list, tuple, dict, bytes) + string_types: - return obj[index] + try: + return obj[index] + except (KeyError, IndexError): + raise EvaluationError("can't lookup key %r on %r" % (index, obj)) raise ValueError('unsafe to lookup on object of type %s' % (type(obj), )) @@ -141,5 +172,5 @@ def parse_trees(cursor_offset, line): raise EvaluationError("Corresponding ASTs to right of cursor are invalid") try: return simple_eval(largest_ast, namespace) - except (ValueError, KeyError, IndexError, AttributeError): + except ValueError: raise EvaluationError("Could not safely evaluate") diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index e591d11a1..da1708adf 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -65,7 +65,7 @@ def test_function_calls_raise(self): simple_eval('1()') def test_nonexistant_names_raise(self): - with self.assertRaises((KeyError, AttributeError)): + with self.assertRaises(EvaluationError): simple_eval('a') class TestEvaluateCurrentExpression(unittest.TestCase): From 4c0ea6b8fd1075b25dce5e98800c74dab72c00b4 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 2 Dec 2015 11:13:08 -0500 Subject: [PATCH 0671/1650] improve function docstring lookup --- bpython/repl.py | 13 ++++++++++--- bpython/simpleeval.py | 21 ++++++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 5bb694ed2..ea87bcc0c 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -51,6 +51,7 @@ from bpython.paste import PasteHelper, PastePinnwand, PasteFailed from bpython.patch_linecache import filename_for_console_input from bpython.translations import _, ngettext +from bpython import simpleeval class RuntimeTimer(object): @@ -518,7 +519,15 @@ def get_args(self): return False try: - f = self.get_object(func) + if inspection.is_eval_safe_name(func): + f = self.get_object(func) + else: + try: + fake_cursor = self.current_line.index(func) + len(func) + f = simpleeval.evaluate_current_attribute( + fake_cursor, self.current_line, self.interp.locals) + except simpleeval.EvaluationError: + return False except Exception: # another case of needing to catch every kind of error # since user code is run in the case of descriptors @@ -624,8 +633,6 @@ def complete(self, tab=False): current_block='\n'.join(self.buffer + [self.current_line]), complete_magic_methods=self.config.complete_magic_methods, history=self.history) - # TODO implement completer.shown_before_tab == False (filenames - # shouldn't fill screen) if len(matches) == 0: self.matches_iter.clear() diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 650977aa7..2b43192a9 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -8,7 +8,6 @@ import ast from six import string_types from six.moves import builtins -from numbers import Number from bpython import line as line_properties from bpython._py3compat import py3 @@ -136,14 +135,16 @@ def find_attribute_with_name(node, name): return r -def evaluate_current_expression(cursor_offset, line, namespace={}): +def evaluate_current_expression(cursor_offset, line, namespace=None): """ - Return evaluted expression to the right of the dot of current attribute. + Return evaluated expression to the right of the dot of current attribute. build asts from with increasing numbers of characters. Find the biggest valid ast. Once our attribute access is a subtree, stop """ + if namespace is None: + namespace = {} # in case attribute is blank, e.g. foo.| -> foo.xxx| temp_line = line[:cursor_offset] + 'xxx' + line[cursor_offset:] @@ -174,3 +175,17 @@ def parse_trees(cursor_offset, line): return simple_eval(largest_ast, namespace) except ValueError: raise EvaluationError("Could not safely evaluate") + + +def evaluate_current_attribute(cursor_offset, line, namespace=None): + # this function runs user code in case of custom descriptors, + # so could fail in any way + + obj = evaluate_current_expression(cursor_offset, line, namespace) + attr = line_properties.current_expression_attribute(cursor_offset, line) + if attr is None: + raise EvaluationError("No attribute found to look up") + try: + return getattr(obj, attr.word) + except AttributeError: + raise EvaluationError("can't lookup attribute %s on %r" % (attr.word, obj)) From 246a5ee87b13e1c696b4837e8d11c21458174737 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 2 Dec 2015 13:18:14 -0500 Subject: [PATCH 0672/1650] Add Python license for two functions --- LICENSE | 52 +++++++++++++++++++++++++++++++++++++++++++ bpython/simpleeval.py | 46 ++++++++++++++++++++++++++++++++++---- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 57ce59d81..c09e66109 100644 --- a/LICENSE +++ b/LICENSE @@ -20,3 +20,55 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Two functions in bpython/simpleeval.py are licensed under the +Python Software Foundation License version 2: simple_eval and find_attribute_with_name + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python +alone or in any derivative version, provided, however, that PSF's +License Agreement and PSF's notice of copyright, i.e., "Copyright (c) +2001, 2002, 2003, 2004 Python Software Foundation; All Rights Reserved" +are retained in Python alone or in any derivative version prepared +by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 2b43192a9..ac8914a97 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -1,4 +1,27 @@ # encoding: utf-8 + +# The MIT License +# +# Copyright (c) 2015 the bpython authors. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# """simple evaluation of side-effect free code In order to provide fancy completion, some code can be executed safely. @@ -26,6 +49,16 @@ def safe_eval(expr, namespace): raise EvaluationError +# This function is under the Python License, Version 2 +# This license requires modifications to the code be reported. +# Based on ast.literal_eval in Python 2 and Python 3 +# Modifications: +# * Python 2 and Python 3 versions of the function are combined +# * checks that objects used as operands of + and - are numbers +# instead of checking they are constructed with number literals +# * new docstring describing different functionality +# * looks up names from namespace +# * indexing syntax is allowed def simple_eval(node_or_string, namespace=None): """ Safely evaluate an expression node or a string containing a Python @@ -40,7 +73,6 @@ def simple_eval(node_or_string, namespace=None): The optional namespace dict-like ought not to cause side effects on lookup """ - # Based heavily on stdlib ast.literal_eval if namespace is None: namespace = {} if isinstance(node_or_string, string_types): @@ -118,11 +150,18 @@ def safe_getitem(obj, index): raise ValueError('unsafe to lookup on object of type %s' % (type(obj), )) +# This function is under the Python License, Version 2 +# This license requires modifications to the code be reported. +# Based on ast.NodeVisitor.generic_visit +# Modifications: +# * Now a standalone function instead of method +# * now hardcoded to look for Attribute node with given attr name +# * returns values back up the recursive call stack to stop once target found def find_attribute_with_name(node, name): - """Based on ast.NodeVisitor""" + """Search depth-first for getitem indexing with name""" if isinstance(node, ast.Attribute) and node.attr == name: return node - for field, value in ast.iter_fields(node): + for _, value in ast.iter_fields(node): if isinstance(value, list): for item in value: if isinstance(item, ast.AST): @@ -134,7 +173,6 @@ def find_attribute_with_name(node, name): if r: return r - def evaluate_current_expression(cursor_offset, line, namespace=None): """ Return evaluated expression to the right of the dot of current attribute. From c5381153c48dbd4f93c152194abe86b7e731e1e9 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 2 Dec 2015 13:44:20 -0500 Subject: [PATCH 0673/1650] Fix Python 3.3 build apparently ast.NameConstant was added in 3.4 --- bpython/simpleeval.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index ac8914a97..adeb788f4 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -81,9 +81,14 @@ def simple_eval(node_or_string, namespace=None): node_or_string = node_or_string.body string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) - name_type_nodes = (ast.Name, ast.NameConstant) if py3 else (ast.Name,) numeric_types = (int, float, complex) + (() if py3 else (long,)) + # added in Python 3.4 + if hasattr(ast, 'NameConstant'): + name_type_nodes = (ast.Name, ast.NameConstant) + else: + name_type_nodes = (ast.Name,) + def _convert(node): if isinstance(node, string_type_nodes): return node.s From c9ded30b0a447f04fe605d6a27a6763deb36dce3 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 3 Dec 2015 10:31:08 -0500 Subject: [PATCH 0674/1650] make constants global --- bpython/simpleeval.py | 26 +++++++++++++------------- bpython/test/test_curtsies_repl.py | 28 +++++++++++++++++----------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index adeb788f4..31ebda45d 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -35,6 +35,15 @@ from bpython import line as line_properties from bpython._py3compat import py3 +_string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) +_numeric_types = (int, float, complex) + (() if py3 else (long,)) + +# added in Python 3.4 +if hasattr(ast, 'NameConstant'): + _name_type_nodes = (ast.Name, ast.NameConstant) +else: + _name_type_nodes = (ast.Name,) + class EvaluationError(Exception): """Raised if an exception occurred in safe_eval.""" @@ -80,17 +89,8 @@ def simple_eval(node_or_string, namespace=None): if isinstance(node_or_string, ast.Expression): node_or_string = node_or_string.body - string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) - numeric_types = (int, float, complex) + (() if py3 else (long,)) - - # added in Python 3.4 - if hasattr(ast, 'NameConstant'): - name_type_nodes = (ast.Name, ast.NameConstant) - else: - name_type_nodes = (ast.Name,) - def _convert(node): - if isinstance(node, string_type_nodes): + if isinstance(node, _string_type_nodes): return node.s elif isinstance(node, ast.Num): return node.n @@ -103,7 +103,7 @@ def _convert(node): in zip(node.keys, node.values)) # this is a deviation from literal_eval: we allow non-literals - elif isinstance(node, name_type_nodes): + elif isinstance(node, _name_type_nodes): try: return namespace[node.id] except KeyError: @@ -117,7 +117,7 @@ def _convert(node): isinstance(node.op, (ast.UAdd, ast.USub)): # ast.literal_eval does ast typechecks here, we use type checks operand = _convert(node.operand) - if not type(operand) in numeric_types: + if not type(operand) in _numeric_types: raise ValueError("unary + and - only allowed on builtin nums") if isinstance(node.op, ast.UAdd): return + operand @@ -128,7 +128,7 @@ def _convert(node): # ast.literal_eval does ast typechecks here, we use type checks left = _convert(node.left) right = _convert(node.right) - if not (type(left) in numeric_types and type(right) in numeric_types): + if not (type(left) in _numeric_types and type(right) in _numeric_types): raise ValueError("binary + and - only allowed on builtin nums") if isinstance(node.op, ast.Add): return left + right diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 2829a2883..1820a530c 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -299,6 +299,8 @@ def setUp(self): self.open = partial(io.open, mode='wt', encoding='utf-8') self.dont_write_bytecode = sys.dont_write_bytecode sys.dont_write_bytecode = True + self.sys_path = sys.path #? + sys.path = self.sys_path[:] #? # Because these tests create Python source files at runtime, # it's possible in Python >=3.3 for the importlib.machinery.FileFinder @@ -316,6 +318,7 @@ def setUp(self): def tearDown(self): sys.dont_write_bytecode = self.dont_write_bytecode + sys.path = self.sys_path #? def push(self, line): self.repl._current_line = line @@ -334,9 +337,11 @@ def tempfile(): def test_module_content_changed(self): with self.tempfile() as (fullpath, path, modname): + print(modname) with self.open(fullpath) as f: f.write('a = 0\n') self.head(path) + print(sys.path) self.push('import %s' % (modname)) self.push('a = %s.a' % (modname)) self.assertIn('a', self.repl.interp.locals) @@ -349,24 +354,25 @@ def test_module_content_changed(self): def test_import_module_with_rewind(self): with self.tempfile() as (fullpath, path, modname): + print(modname) with self.open(fullpath) as f: f.write('a = 0\n') self.head(path) - self.push('import %s' % (modname)) - self.assertIn(modname, self.repl.interp.locals) + self.push('import %s' % (modname)) # SOMETIMES THIS MAKES THE OTHER TEST FAIL!!! + #self.assertIn(modname, self.repl.interp.locals) self.repl.undo() - self.assertNotIn(modname, self.repl.interp.locals) + #self.assertNotIn(modname, self.repl.interp.locals) self.repl.clear_modules_and_reevaluate() - self.assertNotIn(modname, self.repl.interp.locals) - self.push('import %s' % (modname)) - self.push('a = %s.a' % (modname)) - self.assertIn('a', self.repl.interp.locals) - self.assertEqual(self.repl.interp.locals['a'], 0) + #self.assertNotIn(modname, self.repl.interp.locals) + #self.push('import %s' % (modname)) + #self.push('a = %s.a' % (modname)) + #self.assertIn('a', self.repl.interp.locals) + #self.assertEqual(self.repl.interp.locals['a'], 0) with self.open(fullpath) as f: f.write('a = 1\n') - self.repl.clear_modules_and_reevaluate() - self.assertIn('a', self.repl.interp.locals) - self.assertEqual(self.repl.interp.locals['a'], 1) + #self.repl.clear_modules_and_reevaluate() + #self.assertIn('a', self.repl.interp.locals) + #self.assertEqual(self.repl.interp.locals['a'], 1) class TestCurtsiesPagerText(TestCase): From c86f222da5bfa6076dcc8064db7e34f2bf8d215b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 3 Dec 2015 11:29:20 -0500 Subject: [PATCH 0675/1650] Replace copied code with original code --- LICENSE | 4 ++-- bpython/simpleeval.py | 24 +++++------------------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/LICENSE b/LICENSE index c09e66109..72d02ff63 100644 --- a/LICENSE +++ b/LICENSE @@ -21,8 +21,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Two functions in bpython/simpleeval.py are licensed under the -Python Software Foundation License version 2: simple_eval and find_attribute_with_name +One function in bpython/simpleeval.py is licensed under the +Python Software Foundation License version 2: simple_eval PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 31ebda45d..56b0dea3b 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -155,28 +155,14 @@ def safe_getitem(obj, index): raise ValueError('unsafe to lookup on object of type %s' % (type(obj), )) -# This function is under the Python License, Version 2 -# This license requires modifications to the code be reported. -# Based on ast.NodeVisitor.generic_visit -# Modifications: -# * Now a standalone function instead of method -# * now hardcoded to look for Attribute node with given attr name -# * returns values back up the recursive call stack to stop once target found def find_attribute_with_name(node, name): - """Search depth-first for getitem indexing with name""" if isinstance(node, ast.Attribute) and node.attr == name: return node - for _, value in ast.iter_fields(node): - if isinstance(value, list): - for item in value: - if isinstance(item, ast.AST): - r = find_attribute_with_name(item, name) - if r: - return r - elif isinstance(value, ast.AST): - r = find_attribute_with_name(value, name) - if r: - return r + for item in ast.iter_child_nodes(node): + r = find_attribute_with_name(item, name) + if r: + return r + def evaluate_current_expression(cursor_offset, line, namespace=None): """ From 6b1d291e023e420488bdfab5c6674ebb22f64c6c Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 3 Dec 2015 12:13:57 -0500 Subject: [PATCH 0676/1650] test more simple_eval functionality --- bpython/autocomplete.py | 41 +++++++++++++++++---------------- bpython/simpleeval.py | 9 +++++--- bpython/test/test_repl.py | 7 ++++++ bpython/test/test_simpleeval.py | 18 +++++++++++++++ 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 7855c3225..97c2ef179 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -74,6 +74,22 @@ def after_last_dot(name): return name.rstrip('.').rsplit('.')[-1] +def few_enough_underscores(current, match): + """Returns whether match should be shown based on current + + if current is _, True if match starts with 0 or 1 underscore + if current is __, True regardless of match + otherwise True if match does not start with any underscore + """ + if current.startswith('__'): + return True + elif current.startswith('_') and not match.startswith('__'): + return True + elif match.startswith('_'): + return False + else: + return True + def method_match_simple(word, size, text): return word[:size] == text @@ -255,18 +271,9 @@ def matches(self, cursor_offset, line, **kwargs): matches = set(''.join([r.word[:-i], m]) for m in self.attr_matches(methodtext, locals_)) - # TODO add open paren for methods via _callable_prefix (or decide not - # to) unless the first character is a _ filter out all attributes - # starting with a _ - if r.word.split('.')[-1].startswith('__'): - pass - elif r.word.split('.')[-1].startswith('_'): - matches = set(match for match in matches - if not match.split('.')[-1].startswith('__')) - else: - matches = set(match for match in matches - if not match.split('.')[-1].startswith('_')) - return matches + return set(m for m in matches + if few_enough_underscores(r.word.split('.')[-1], + m.split('.')[-1])) def locate(self, current_offset, line): return lineparts.current_dotted_attribute(current_offset, line) @@ -470,14 +477,8 @@ def matches(self, cursor_offset, line, **kwargs): # strips leading dot matches = [m[1:] for m in self.attr_lookup(obj, '', attr.word)] - if attr.word.startswith('__'): - pass - elif attr.word.startswith('_'): - matches = set(match for match in matches - if not match.startswith('__')) - else: - matches = set(match for match in matches - if not match.split('.')[-1].startswith('_')) + + return set(m for m in matches if few_enough_underscores(attr.word, m)) return matches diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 56b0dea3b..e27801be6 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -168,10 +168,12 @@ def evaluate_current_expression(cursor_offset, line, namespace=None): """ Return evaluated expression to the right of the dot of current attribute. - build asts from with increasing numbers of characters. - Find the biggest valid ast. - Once our attribute access is a subtree, stop + Only evaluates builtin objects, and do any attribute lookup. """ + # Builds asts from with increasing numbers of characters back from cursor. + # Find the biggest valid ast. + # Once our attribute access is found, return its .value subtree + if namespace is None: namespace = {} @@ -207,6 +209,7 @@ def parse_trees(cursor_offset, line): def evaluate_current_attribute(cursor_offset, line, namespace=None): + """Safely evaluates the expression attribute lookup currently occuring on""" # this function runs user code in case of custom descriptors, # so could fail in any way diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index ce66d354e..7292b2810 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -230,6 +230,13 @@ def test_issue583(self): self.repl.set_docstring() self.assertIsNot(self.repl.docstring, None) + def test_methods_of_expressions(self): + self.set_input_line("'a'.capitalize(") + self.assertTrue(self.repl.get_args()) + + self.set_input_line("(1 + 1).bit_length(") + self.assertTrue(self.repl.get_args()) + class TestGetSource(unittest.TestCase): def setUp(self): diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index da1708adf..06b1dadb8 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import ast +import numbers from bpython.simpleeval import (simple_eval, evaluate_current_expression, @@ -60,6 +61,23 @@ def __getattr__(inner_self, attr): with self.assertRaises(ValueError): simple_eval('a[1]', {'a': SchrodingersDict()}) + def test_operators_on_suspicious_types(self): + class Spam(numbers.Number): + def __add__(inner_self, other): + self.fail("doing attribute lookup might have side effects") + + with self.assertRaises(ValueError): + simple_eval('a + 1', {'a': Spam()}) + + def test_operators_on_numbers(self): + self.assertEqual(simple_eval('-2'), -2) + self.assertEqual(simple_eval('1 + 1'), 2) + self.assertEqual(simple_eval('a - 2', {'a':1}), -1) + with self.assertRaises(ValueError): + simple_eval('2 * 3') + with self.assertRaises(ValueError): + simple_eval('2 ** 3') + def test_function_calls_raise(self): with self.assertRaises(ValueError): simple_eval('1()') From dbba924f019d594cbfab65e9e72d1219c396163d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 3 Dec 2015 12:57:46 -0500 Subject: [PATCH 0677/1650] fix Python 2.6 build --- bpython/test/test_repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 7292b2810..093dbc293 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -234,7 +234,7 @@ def test_methods_of_expressions(self): self.set_input_line("'a'.capitalize(") self.assertTrue(self.repl.get_args()) - self.set_input_line("(1 + 1).bit_length(") + self.set_input_line("(1 + 1.1).as_integer_ratio(") self.assertTrue(self.repl.get_args()) From 1bc66fc59bb663a00568db2db05af3b860d53c45 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 11 Dec 2015 12:31:04 -0500 Subject: [PATCH 0678/1650] first refactor step --- bpython/curtsies.py | 115 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 3 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index ea69fdeed..44c6778d3 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -25,10 +25,114 @@ logger = logging.getLogger(__name__) -repl = None # global for `from bpython.curtsies import repl` +#repl = None # global for `from bpython.curtsies import repl` # WARNING Will be a problem if more than one repl is ever instantiated this way +class FullCurtsiesRepl(Repl): + def __init__(self, config, locals_, banner, interp=None, + paste=None, interactive=True): + self.input_generator = curtsies.input.Input( + keynames='curtsies', + sigint_event=True, + paste_threshold=None) + self.window = curtsies.window.CursorAwareWindow( + sys.stdout, + sys.stdin, + keep_last_line=True, + hide_cursor=False, + extra_bytes_callback=self.input_generator.unget_bytes) + + self.request_refresh = self.input_generator.event_trigger( + bpythonevents.RefreshRequestEvent) + self.schedule_refresh = self.input_generator.scheduled_event_trigger( + bpythonevents.ScheduledRefreshRequestEvent) + self.request_reload = self.input_generator.threadsafe_event_trigger( + bpythonevents.ReloadEvent) + self.interrupting_refresh = (self.input_generator + .threadsafe_event_trigger(lambda: None)) + self.request_undo = self.input_generator.event_trigger( + bpythonevents.UndoEvent) + + with self.input_generator: + pass # temp hack to get .original_stty + + Repl.__init__(self, + config=config, + locals_=locals_, + request_refresh=self.request_refresh, + schedule_refresh=self.schedule_refresh, + request_reload=self.request_reload, + request_undo=self.request_undo, + get_term_hw=self.window.get_term_hw, + get_cursor_vertical_diff=self.window.get_cursor_vertical_diff, + banner=banner, + interp=interp, + interactive=interactive, + orig_tcattrs=self.input_generator.original_stty, + on_suspend=self.on_suspend, + after_suspend=self.after_suspend) + + + def on_suspend(self): + self.window.__exit__(None, None, None) + self.input_generator.__exit__(None, None, None) + + def after_suspend(self): + self.input_generator.__enter__() + self.window.__enter__() + self.interrupting_refresh() + + def process_event(self, e): + """If None is passed in, just paint the screen""" + try: + if e is not None: + Repl.process_event(self, e) + except (SystemExitFromCodeGreenlet, SystemExit) as err: + array, cursor_pos = self.paint( + about_to_exit=True, + user_quit=isinstance(err, + SystemExitFromCodeGreenlet)) + scrolled = self.window.render_to_terminal(array, cursor_pos) + self.scroll_offset += scrolled + raise + else: + array, cursor_pos = self.paint() + scrolled = self.window.render_to_terminal(array, cursor_pos) + self.scroll_offset += scrolled + + def mainloop(self, interactive=True, paste=None): + if interactive: + # Add custom help command + # TODO: add methods to run the code + self.coderunner.interp.locals['_repl'] = self + + self.coderunner.interp.runsource( + 'from bpython.curtsiesfrontend._internal ' + 'import _Helper') + self.coderunner.interp.runsource('help = _Helper(_repl)\n') + + del self.coderunner.interp.locals['_repl'] + del self.coderunner.interp.locals['_Helper'] + + # run startup file + self.process_event(bpythonevents.RunStartupFileEvent()) + + # handle paste + if paste: + self.process_event(paste) + + # do a display before waiting for first event + self.process_event(None) + inputs = combined_events(self.input_generator) + for unused in find_iterator: + e = inputs.send(0) + if e is not None: + self.process_event(e) + + for e in inputs: + self.process_event(e) + def main(args=None, locals_=None, banner=None, welcome_message=None): """ banner is displayed directly after the version information. @@ -85,8 +189,13 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): if banner is not None: print(banner) try: - exit_value = mainloop(config, locals_, welcome_message, interp, paste, - interactive=(not exec_args)) + r = FullCurtsiesRepl(config, locals_, welcome_message, interp, paste, + interactive=(not exec_args)) + with r.input_generator: + with r.window as win: + with r: + r.height, r.width = win.t.height, win.t.width + exit_value = r.mainloop() except (SystemExitFromCodeGreenlet, SystemExit) as e: exit_value = e.args return extract_exit_value(exit_value) From a9de650b3bdc4554000afa0b9542ef5c2c6e42e8 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 11 Dec 2015 12:50:48 -0500 Subject: [PATCH 0679/1650] Refactor callbacks into methods * create SimpleRepl class for testing and as an example * rename curtsies repl to BaseRepl * document BaseRepl methods to override --- bpython/curtsies.py | 69 ++++---- bpython/curtsiesfrontend/repl.py | 216 +++++++++++-------------- bpython/simplerepl.py | 105 ++++++++++++ bpython/test/test_curtsies_painting.py | 13 +- bpython/test/test_curtsies_repl.py | 2 +- 5 files changed, 241 insertions(+), 164 deletions(-) create mode 100644 bpython/simplerepl.py diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 44c6778d3..df33c816f 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -12,7 +12,7 @@ import curtsies.input import curtsies.events -from bpython.curtsiesfrontend.repl import Repl +from bpython.curtsiesfrontend.repl import BaseRepl from bpython.curtsiesfrontend.coderunner import SystemExitFromCodeGreenlet from bpython import args as bpargs from bpython import translations @@ -25,29 +25,29 @@ logger = logging.getLogger(__name__) -#repl = None # global for `from bpython.curtsies import repl` +repl = None # global for `from bpython.curtsies import repl` # WARNING Will be a problem if more than one repl is ever instantiated this way -class FullCurtsiesRepl(Repl): +class FullCurtsiesRepl(BaseRepl): def __init__(self, config, locals_, banner, interp=None, - paste=None, interactive=True): + paste=None): self.input_generator = curtsies.input.Input( - keynames='curtsies', - sigint_event=True, - paste_threshold=None) + keynames='curtsies', + sigint_event=True, + paste_threshold=None) self.window = curtsies.window.CursorAwareWindow( - sys.stdout, - sys.stdin, - keep_last_line=True, - hide_cursor=False, - extra_bytes_callback=self.input_generator.unget_bytes) + sys.stdout, + sys.stdin, + keep_last_line=True, + hide_cursor=False, + extra_bytes_callback=self.input_generator.unget_bytes) - self.request_refresh = self.input_generator.event_trigger( + self._request_refresh = self.input_generator.event_trigger( bpythonevents.RefreshRequestEvent) - self.schedule_refresh = self.input_generator.scheduled_event_trigger( + self._schedule_refresh = self.input_generator.scheduled_event_trigger( bpythonevents.ScheduledRefreshRequestEvent) - self.request_reload = self.input_generator.threadsafe_event_trigger( + self._request_reload = self.input_generator.threadsafe_event_trigger( bpythonevents.ReloadEvent) self.interrupting_refresh = (self.input_generator .threadsafe_event_trigger(lambda: None)) @@ -57,22 +57,21 @@ def __init__(self, config, locals_, banner, interp=None, with self.input_generator: pass # temp hack to get .original_stty - Repl.__init__(self, - config=config, + BaseRepl.__init__(self, locals_=locals_, - request_refresh=self.request_refresh, - schedule_refresh=self.schedule_refresh, - request_reload=self.request_reload, - request_undo=self.request_undo, - get_term_hw=self.window.get_term_hw, - get_cursor_vertical_diff=self.window.get_cursor_vertical_diff, + config=config, banner=banner, interp=interp, - interactive=interactive, - orig_tcattrs=self.input_generator.original_stty, - on_suspend=self.on_suspend, - after_suspend=self.after_suspend) + orig_tcattrs=self.input_generator.original_stty) + + def get_term_hw(self): + return self.window.get_term_hw() + + def get_cursor_vertical_diff(self): + return self.window.get_cursor_vertical_diff() + def get_top_usable_line(self): + return self.window.top_usable_row def on_suspend(self): self.window.__exit__(None, None, None) @@ -87,7 +86,7 @@ def process_event(self, e): """If None is passed in, just paint the screen""" try: if e is not None: - Repl.process_event(self, e) + BaseRepl.process_event(self, e) except (SystemExitFromCodeGreenlet, SystemExit) as err: array, cursor_pos = self.paint( about_to_exit=True, @@ -188,14 +187,14 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): print(bpargs.version_banner()) if banner is not None: print(banner) + global repl + repl = FullCurtsiesRepl(config, locals_, welcome_message, interp, paste) try: - r = FullCurtsiesRepl(config, locals_, welcome_message, interp, paste, - interactive=(not exec_args)) - with r.input_generator: - with r.window as win: - with r: - r.height, r.width = win.t.height, win.t.width - exit_value = r.mainloop() + with repl.input_generator: + with repl.window as win: + with repl: + repl.height, repl.width = win.t.height, win.t.width + exit_value = repl.mainloop() except (SystemExitFromCodeGreenlet, SystemExit) as e: exit_value = e.args return extract_exit_value(exit_value) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 65dc0d070..6dc55be20 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -11,7 +11,6 @@ import subprocess import sys import tempfile -import threading import time import unicodedata from six.moves import range @@ -32,7 +31,7 @@ from bpython.config import (Struct, loadini, default_config_path, getpreferredencoding) from bpython.formatter import BPythonFormatter -from bpython import autocomplete, importcompletion +from bpython import autocomplete from bpython.translations import _ from bpython._py3compat import py3 from bpython.pager import get_pager_command @@ -277,7 +276,7 @@ def find_module(self, fullname, path=None): return None -class Repl(BpythonRepl): +class BaseRepl(BpythonRepl): """Python Repl Reacts to events like @@ -290,49 +289,27 @@ class Repl(BpythonRepl): outputs: - 2D array to be rendered - Repl is mostly view-independent state of Repl - but self.width and + BaseRepl is mostly view-independent state of Repl - but self.width and self.height are important for figuring out how to wrap lines for example. Usually self.width and self.height should be set by receiving a window resize event, not manually set to anything - as long as the first event received is a window resize event, this works fine. + + Subclasses are responsible for implementing several methods. """ def __init__(self, locals_=None, config=None, - request_refresh=lambda: None, - schedule_refresh=lambda when=0: None, - request_reload=lambda desc: None, - request_undo=lambda n=1: None, - get_term_hw=lambda: (50, 10), - get_cursor_vertical_diff=lambda: 0, banner=None, interp=None, - interactive=True, - orig_tcattrs=None, - on_suspend=lambda *args: None, - after_suspend=lambda *args: None, - get_top_usable_line=lambda: 0): + orig_tcattrs=None): """ locals_ is a mapping of locals to pass into the interpreter config is a bpython config.Struct with config attributes - request_refresh is a function that will be called when the Repl - wants to refresh the display, but wants control returned to it - afterwards - schedule_refresh is the same, but takes as a kwarg when= of when to - fire. Scheduled refreshes interrupt waiting for keyboard input - request_reload is like request_refresh, but for a different event - request_undo is like reload, but for a different event - get_term_hw is a function that returns the current width and height of - the terminal - get_cursor_vertical_diff is a function that returns how the cursor - moved due to a window size change banner is a string to display briefly in the status bar interp is an interpreter instance to use original terminal state, useful for shelling out with normal terminal - on_suspend will be called on sigtstp - after_suspend will be called when process foregrounded after suspend - get_top_usable_line returns the top line of the terminal owned """ logger.debug("starting init") @@ -362,35 +339,12 @@ def __init__(self, self.reevaluating = False self.fake_refresh_requested = False - def smarter_request_refresh(): - if self.reevaluating or self.paste_mode: - self.fake_refresh_requested = True - else: - request_refresh() - self.request_refresh = smarter_request_refresh - - def smarter_schedule_refresh(when='now'): - if self.reevaluating or self.paste_mode: - self.fake_refresh_requested = True - else: - schedule_refresh(when=when) - self.schedule_refresh = smarter_schedule_refresh - - def smarter_request_reload(files_modified=()): - if self.watching_files: - request_reload(files_modified=files_modified) - - self.request_reload = smarter_request_reload - self.request_undo = request_undo - self.get_term_hw = get_term_hw - self.get_cursor_vertical_diff = get_cursor_vertical_diff - self.status_bar = StatusBar('', request_refresh=self.request_refresh, schedule_refresh=self.schedule_refresh) self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch) logger.debug("starting parent init") - super(Repl, self).__init__(interp, config) + super(BaseRepl, self).__init__(interp, config) self.formatter = BPythonFormatter(config.color_scheme) @@ -425,9 +379,6 @@ def smarter_request_reload(files_modified=()): self._cursor_offset = 0 self.orig_tcattrs = orig_tcattrs - self.on_suspend = on_suspend - self.after_suspend = after_suspend - self.get_top_usable_line = get_top_usable_line self.coderunner = CodeRunner(self.interp, self.request_refresh) self.stdout = FakeOutput(self.coderunner, self.send_to_stdout) @@ -470,7 +421,90 @@ def smarter_request_reload(files_modified=()): self.status_bar.message(banner) - self.watcher = ModuleChangedEventHandler([], smarter_request_reload) + self.watcher = ModuleChangedEventHandler([], self.request_reload) + + ### These methods should be overridden, but the default implementations below + ### can be used as well. + + def get_cursor_vertical_diff(self): + """Return how the cursor moved due to a window size change""" + return 0 + + def get_top_usable_line(self): + """Return the top line of display that can be rewritten""" + return 0 + + def get_term_hw(self): + """Returns the current width and height of the display area.""" + return (50, 10) + + def _schedule_refresh(self, when='now'): + """Arrange for the bpython display to be refreshed soon. + + This method will be called when the Repl wants the display to be + refreshed at a known point in the future, and as such it should + interrupt a pending request to the user for input. + + Because the worst-case effect of not refreshing + is only having an out of date UI until the user enters input, a + default NOP implementation is provided.""" + + ### These methods must be overridden in subclasses + + def _request_refresh(self): + """Arrange for the bpython display to be refreshed soon. + + This method will be called when the Repl wants to refresh the display, + but wants control returned to it afterwards. (it is assumed that simply + returning from process_event will cause an event refresh) + + The very next event received by process_event should be a + RefreshRequestEvent.""" + raise NotImplementedError + + def _request_reload(self, files_modified=('?',)): + """Like request_refresh, but for reload requests events.""" + raise NotImplementedError + + def request_undo(self, n=1): + """ike request_refresh, but for undo request events.""" + raise NotImplementedError + + def on_suspend(): + """Will be called on sigtstp. + + Do whatever cleanup would allow the user to use other programs.""" + raise NotImplementedError + + def after_suspend(): + """Will be called when process foregrounded after suspend. + + See to it that process_event is called with None to trigger a refresh + if not in the middle of a process_event call when suspend happened.""" + raise NotImplementedError + + ### End methods that should be overridden in subclass + + def request_refresh(self): + """Arrange for the bpython display to be refreshed soon.""" + if self.reevaluating or self.paste_mode: + self.fake_refresh_requested = True + else: + self._request_refresh() + + def request_reload(self, files_modified=()): + """Arrange for the """ + if self.watching_files: + self._request_reload(files_modified=files_modified) + + def schedule_refresh(self, when='now'): + """Schedule a ScheduledRefreshRequestEvent for when. + + Such a event should interrupt if blockied waiting for keyboard input""" + if self.reevaluating or self.paste_mode: + self.fake_refresh_requested = True + else: + self._schedule_refresh(when=when) def __enter__(self): self.orig_stdout = sys.stdout @@ -1364,55 +1398,9 @@ def in_paste_mode(self): if not self.paste_mode: self.update_completion() - # Debugging shims, good example of embedding a Repl in other code - def dumb_print_output(self): - arr, cpos = self.paint() - arr[cpos[0]:cpos[0]+1, cpos[1]:cpos[1]+1] = ['~'] - - def my_print(msg): - self.orig_stdout.write(str(msg)+'\n') - - my_print('X'*(self.width+8)) - my_print(' use "/" for enter '.center(self.width+8, 'X')) - my_print(' use "\\" for rewind '.center(self.width+8, 'X')) - my_print(' use "|" to raise an error '.center(self.width+8, 'X')) - my_print(' use "$" to pastebin '.center(self.width+8, 'X')) - my_print(' "~" is the cursor '.center(self.width+8, 'X')) - my_print('X'*(self.width+8)) - my_print('X``'+('`'*(self.width+2))+'``X') - for line in arr: - my_print('X```'+line.ljust(self.width)+'```X') - logger.debug('line:') - logger.debug(repr(line)) - my_print('X``'+('`'*(self.width+2))+'``X') - my_print('X'*(self.width+8)) - return max(len(arr) - self.height, 0) - - def dumb_input(self, requested_refreshes=[]): - chars = list(self.orig_stdin.readline()[:-1]) - while chars or requested_refreshes: - if requested_refreshes: - requested_refreshes.pop() - self.process_event(bpythonevents.RefreshRequestEvent()) - continue - c = chars.pop(0) - if c in '/': - c = '\n' - elif c in '\\': - c = '' - elif c in '|': - def r(): - raise Exception('errors in other threads should look like this') - t = threading.Thread(target=r) - t.daemon = True - t.start() - elif c in '$': - c = key_dispatch[self.config.pastebin_key][0] - self.process_event(c) - def __repr__(self): s = '' - s += ' "/", rewind -> "\\", '.center(self.width+8, 'X')) + self.my_print(' reload -> "|", pastebin -> "$", '.center(self.width+8, 'X')) + self.my_print(' "~" is the cursor '.center(self.width+8, 'X')) + self.my_print('X'*(self.width+8)) + self.my_print('X``'+('`'*(self.width+2))+'``X') + for line in arr: + self.my_print('X```'+unicode(line.ljust(self.width))+'```X') + logger.debug('line:') + logger.debug(repr(line)) + self.my_print('X``'+('`'*(self.width+2))+'``X') + self.my_print('X'*(self.width+8)) + return max(len(arr) - self.height, 0) + + def get_input(self): + chars = list(self.orig_stdin.readline()[:-1]) + while chars or self.requested_events: + if self.requested_events: + self.process_event(self.requested_events.pop()) + continue + c = chars.pop(0) + if c in '/': + c = '\n' + elif c in '\\': + c = key_dispatch[self.config.undo_key][0] + elif c in '$': + c = key_dispatch[self.config.pastebin_key][0] + elif c in '|': + c = key_dispatch[self.config.reimport_key][0] + self.process_event(c) + + +def main(args=None, locals_=None, banner=None): + translations.init() + while importcompletion.find_coroutine(): + pass + with SimpleRepl() as r: + r.width = 50 + r.height = 10 + while True: + r.print_output() + r.get_input() + + +if __name__ == '__main__': + main() diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 005431128..55174b908 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -10,7 +10,7 @@ from bpython.curtsiesfrontend.events import RefreshRequestEvent from bpython.test import mock from bpython import config, inspection -from bpython.curtsiesfrontend.repl import Repl +from bpython.curtsiesfrontend.repl import BaseRepl from bpython.curtsiesfrontend import replpainter from bpython.repl import History from bpython.curtsiesfrontend.repl import INCONSISTENT_HISTORY_MSG, \ @@ -40,7 +40,10 @@ def tearDownClass(cls): class CurtsiesPaintingTest(FormatStringTest, ClearEnviron): def setUp(self): - self.repl = Repl(config=setup_config()) + class TestRepl(BaseRepl): + def _request_refresh(inner_self): + pass + self.repl = TestRepl(config=setup_config()) # clear history self.repl.rl_history = History() self.repl.height, self.repl.width = (5, 10) @@ -182,8 +185,10 @@ def undo(self): def setUp(self): self.refresh_requests = [] - self.repl = Repl(banner='', config=setup_config(), - request_refresh=self.refresh) + class TestRepl(BaseRepl): + def _request_refresh(inner_self): + self.refresh() + self.repl = TestRepl(banner='', config=setup_config()) # clear history self.repl.rl_history = History() self.repl.height, self.repl.width = (5, 32) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 1820a530c..58c0b84ca 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -232,7 +232,7 @@ def captured_output(): def create_repl(**kwargs): config = setup_config({'editor': 'true'}) - repl = curtsiesrepl.Repl(config=config, **kwargs) + repl = curtsiesrepl.BaseRepl(config=config, **kwargs) os.environ['PAGER'] = 'true' os.environ.pop('PYTHONSTARTUP', None) repl.width = 50 From d5af60664235a5fef8b4f7fa12b735f4e8500311 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 14 Dec 2015 17:13:06 -0500 Subject: [PATCH 0680/1650] docfixes and PEP8 whitespace --- bpython/curtsiesfrontend/repl.py | 65 +++++++++++++++++--------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 6dc55be20..eca7ae98b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -88,9 +88,10 @@ ### To return to bpython without reevaluating make no changes to this file ### or save an empty file. """ -MAX_EVENTS_POSSIBLY_NOT_PASTE = 20 # more than this many events will be assumed to - # be a true paste event, i.e. control characters - # like '' will be stripped + +# more than this many events will be assumed to be a true paste event, +# i.e. control characters like '' will be stripped +MAX_EVENTS_POSSIBLY_NOT_PASTE = 20 # This is needed for is_nop and should be removed once is_nop is fixed. if py3: @@ -152,7 +153,7 @@ def process_event(self, e): self.has_focus = False self.current_line = '' self.cursor_offset = 0 - self.repl.run_code_and_maybe_finish(for_code=line+'\n') + self.repl.run_code_and_maybe_finish(for_code=line + '\n') else: # add normal character self.add_input_character(e) @@ -423,8 +424,8 @@ def __init__(self, self.watcher = ModuleChangedEventHandler([], self.request_reload) - ### These methods should be overridden, but the default implementations below - ### can be used as well. + # The methods below should be overridden, but the default implementations + # below can be used as well. def get_cursor_vertical_diff(self): """Return how the cursor moved due to a window size change""" @@ -449,7 +450,7 @@ def _schedule_refresh(self, when='now'): is only having an out of date UI until the user enters input, a default NOP implementation is provided.""" - ### These methods must be overridden in subclasses + # The methods below must be overridden in subclasses. def _request_refresh(self): """Arrange for the bpython display to be refreshed soon. @@ -467,7 +468,7 @@ def _request_reload(self, files_modified=('?',)): raise NotImplementedError def request_undo(self, n=1): - """ike request_refresh, but for undo request events.""" + """Like request_refresh, but for undo request events.""" raise NotImplementedError def on_suspend(): @@ -483,17 +484,17 @@ def after_suspend(): if not in the middle of a process_event call when suspend happened.""" raise NotImplementedError - ### End methods that should be overridden in subclass + # end methods that should be overridden in subclass def request_refresh(self): - """Arrange for the bpython display to be refreshed soon.""" + """Request that the bpython display to be refreshed soon.""" if self.reevaluating or self.paste_mode: self.fake_refresh_requested = True else: self._request_refresh() def request_reload(self, files_modified=()): - """Arrange for the """ + """Request that a ReloadEvent be passed next into process_event""" if self.watching_files: self._request_reload(files_modified=files_modified) @@ -611,7 +612,6 @@ def process_control_event(self, e): else: self.process_simple_keypress(ee) - elif isinstance(e, bpythonevents.RunStartupFileEvent): try: self.startup() @@ -651,9 +651,9 @@ def process_key_event(self, e): if (e in (key_dispatch[self.config.right_key] + key_dispatch[self.config.end_of_line_key] + - ("",)) - and self.config.curtsies_right_arrow_completion - and self.cursor_offset == len(self.current_line)): + ("",)) and + self.config.curtsies_right_arrow_completion and + self.cursor_offset == len(self.current_line)): self.current_line += self.current_suggestion self.cursor_offset = len(self.current_line) @@ -669,8 +669,9 @@ def process_key_event(self, e): self.incremental_search(reverse=True) elif e in key_dispatch[self.config.incremental_search_key]: self.incremental_search() - elif (e in ("",) + key_dispatch[self.config.backspace_key] - and self.incremental_search_mode): + elif (e in (("",) + + key_dispatch[self.config.backspace_key]) and + self.incremental_search_mode): self.add_to_incremental_search(self, backspace=True) elif e in self.edit_keys.cut_buffer_edits: self.readline_kill(e) @@ -730,7 +731,7 @@ def get_last_word(self): self._set_current_line(line[:len(line) - len(previous_word)] + word, reset_rl_history=False) self._set_cursor_offset( - self.cursor_offset-len(previous_word) + len(word), + self.cursor_offset - len(previous_word) + len(word), reset_rl_history=False) def incremental_search(self, reverse=False, include_current=False): @@ -816,8 +817,8 @@ def only_whitespace_left_of_cursor(): self.list_win_visible = self.complete() elif self.matches_iter.matches: - self.current_match = (back and self.matches_iter.previous() - or next(self.matches_iter)) + self.current_match = (back and self.matches_iter.previous() or + next(self.matches_iter)) self._cursor_offset, self._current_line = self.matches_iter.cur_line() # using _current_line so we don't trigger a completion reset self.list_win_visible = True @@ -827,7 +828,7 @@ def on_control_d(self): raise SystemExit() else: self.current_line = (self.current_line[:self.cursor_offset] + - self.current_line[self.cursor_offset+1:]) + self.current_line[(self.cursor_offset + 1):]) def cut_to_buffer(self): self.cut_buffer = self.current_line[self.cursor_offset:] @@ -886,7 +887,7 @@ def send_session_to_external_editor(self, filename=None): if line.startswith(self.ps1) else line[len(self.ps2):] if line.startswith(self.ps2) else - '### '+line + '### ' + line for line in self.getstdout().split('\n')) text = self.send_to_external_editor(for_editor) if text == for_editor: @@ -1056,7 +1057,7 @@ def run_code_and_maybe_finish(self, for_code=None): self.display_lines.extend(paint.display_linize(self.current_stdouterr_line, self.width)) self.current_stdouterr_line = '' - self._set_current_line(' '*indent, update_completion=True) + self._set_current_line(' ' * indent, update_completion=True) self.cursor_offset = len(self.current_line) def keyboard_interrupt(self): @@ -1226,7 +1227,7 @@ def paint(self, about_to_exit=False, user_quit=False): """ # The hairiest function in the curtsies - a cleanup would be great. if about_to_exit: - self.clean_up_current_line_for_exit() # exception to not changing state! + self.clean_up_current_line_for_exit() # exception to not changing state! width, min_height = self.width, self.height show_status_bar = ((bool(self.status_bar.should_show_message) or self.status_bar.has_focus) @@ -1239,7 +1240,7 @@ def paint(self, about_to_exit=False, user_quit=False): current_line_start_row = len(self.lines_for_display) - max(0, self.scroll_offset) # TODO how is the situation of self.scroll_offset < 0 possible? # current_line_start_row = len(self.lines_for_display) - self.scroll_offset - if self.request_paint_to_clear_screen: # or show_status_bar and about_to_exit ? + if self.request_paint_to_clear_screen: # or show_status_bar and about_to_exit ? self.request_paint_to_clear_screen = False arr = FSArray(min_height + current_line_start_row, width) elif self.request_paint_to_pad_bottom: @@ -1275,9 +1276,11 @@ def move_screen_up(current_line_start_row): self.history_already_messed_up = True msg = INCONSISTENT_HISTORY_MSG arr[0, 0:min(len(msg), width)] = [msg[:width]] - current_line_start_row += 1 # for the message - self.scroll_offset -= 1 # to make up for the scroll we're going to receive - # after we render scrolls down a line + current_line_start_row += 1 # for the message + + # to make up for the scroll that will be received after the + # scrolls are rendered down a line + self.scroll_offset -= 1 current_line_start_row = move_screen_up(current_line_start_row) logger.debug('current_line_start_row: %r', current_line_start_row) @@ -1313,7 +1316,7 @@ def move_screen_up(current_line_start_row): self.inconsistent_history = False - if user_quit: # quit() or exit() in interp + if user_quit: # quit() or exit() in interp current_line_start_row = current_line_start_row - current_line.height logger.debug("---current line row slice %r, %r", current_line_start_row, current_line_start_row + current_line.height) @@ -1322,9 +1325,9 @@ def move_screen_up(current_line_start_row): 0:current_line.width] = current_line if current_line.height > min_height: - return arr, (0, 0) # short circuit, no room for infobox + return arr, (0, 0) # short circuit, no room for infobox - lines = paint.display_linize(self.current_cursor_line+'X', width) + lines = paint.display_linize(self.current_cursor_line + 'X', width) # extra character for space for the cursor current_line_end_row = current_line_start_row + len(lines) - 1 From 03c192e858133119fc6cf9ce6671f44001f28e92 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 14 Dec 2015 17:15:10 -0500 Subject: [PATCH 0681/1650] add license to simplerepl --- bpython/simplerepl.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/bpython/simplerepl.py b/bpython/simplerepl.py index 8e5680c60..4654b5b06 100644 --- a/bpython/simplerepl.py +++ b/bpython/simplerepl.py @@ -1,3 +1,27 @@ +# encoding: utf-8 + +# The MIT License +# +# Copyright (c) 2015 the bpython authors. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + from __future__ import unicode_literals, print_function, absolute_import """An example bpython repl without a nice UI for testing and to demonstrate the methods of bpython.curtsiesrepl.repl.BaseRepl that must be overridden. From 020c273b9a79740fa749c225e68254a90cecf1e6 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 14 Dec 2015 17:24:38 -0500 Subject: [PATCH 0682/1650] PEP8 whitespace --- bpython/simplerepl.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/bpython/simplerepl.py b/bpython/simplerepl.py index 4654b5b06..cb85b4fa2 100644 --- a/bpython/simplerepl.py +++ b/bpython/simplerepl.py @@ -53,20 +53,21 @@ def _schedule_refresh(self, when='now'): if when == 'now': self.request_refresh() else: - self.my_print('please refresh in '+str(round(when - time.time(), 1))+' seconds') + dt = round(when - time.time(), 1) + self.out('please refresh in {} seconds'.format(dt)) def _request_reload(self, files_modified=('?',)): e = bpythonevents.ReloadEvent() e.files_modified = files_modified self.requested_events.append(e) - self.my_print('please hit enter to trigger a refresh') + self.out('please hit enter to trigger a refresh') def request_undo(self, n=1): self.requested_events.append(bpythonevents.UndoEvent(n=n)) - def my_print(self, msg): + def out(self, msg): if hasattr(self, 'orig_stdout'): - self.orig_stdout.write((msg+'\n').encode('utf8')) + self.orig_stdout.write((msg + '\n').encode('utf8')) self.orig_stdout.flush() else: print(msg) @@ -75,24 +76,27 @@ def on_suspend(self): pass def after_suspend(self): - self.my_print('please hit enter to trigger a refresh') + self.out('please hit enter to trigger a refresh') def print_output(self): arr, cpos = self.paint() - arr[cpos[0]:cpos[0]+1, cpos[1]:cpos[1]+1] = ['~'] - - self.my_print('X'*(self.width+8)) - self.my_print(' enter -> "/", rewind -> "\\", '.center(self.width+8, 'X')) - self.my_print(' reload -> "|", pastebin -> "$", '.center(self.width+8, 'X')) - self.my_print(' "~" is the cursor '.center(self.width+8, 'X')) - self.my_print('X'*(self.width+8)) - self.my_print('X``'+('`'*(self.width+2))+'``X') + arr[cpos[0]:cpos[0] + 1, cpos[1]:cpos[1] + 1] = ['~'] + + def print_padded(s): + return self.out(s.center(self.width + 8, 'X')) + + print_padded('') + print_padded(' enter -> "/", rewind -> "\\", ') + print_padded(' reload -> "|", pastebin -> "$", ') + print_padded(' "~" is the cursor ') + print_padded('') + self.out('X``' + ('`' * (self.width + 2)) + '``X') for line in arr: - self.my_print('X```'+unicode(line.ljust(self.width))+'```X') + self.out('X```' + unicode(line.ljust(self.width)) + '```X') logger.debug('line:') logger.debug(repr(line)) - self.my_print('X``'+('`'*(self.width+2))+'``X') - self.my_print('X'*(self.width+8)) + self.out('X``' + ('`' * (self.width + 2)) + '``X') + self.out('X' * (self.width + 8)) return max(len(arr) - self.height, 0) def get_input(self): From 5ce8000d4649e911fc1f941b9964776b43cca19d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 15 Dec 2015 11:00:12 -0500 Subject: [PATCH 0683/1650] PEP8 curtsies Repl --- bpython/curtsiesfrontend/repl.py | 295 +++++++++++++++++++------------ 1 file changed, 183 insertions(+), 112 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index eca7ae98b..55ecac54c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -328,8 +328,8 @@ def __init__(self, interp.write = self.send_to_stderr if banner is None: if config.help_key: - banner = ' '.join((_('Welcome to bpython!'), - _('Press <%s> for help.') % config.help_key)) + banner = (_('Welcome to bpython!') + ' ' + + _('Press <%s> for help.') % config.help_key) else: banner = None # only one implemented currently @@ -411,9 +411,9 @@ def __init__(self, self.watching_files = False # whether auto reloading active # 'reverse_incremental_search', 'incremental_search' or None - self.incremental_search_mode = None + self.incr_search_mode = None - self.incremental_search_target = '' + self.incr_search_target = '' self.original_modules = set(sys.modules.keys()) @@ -671,7 +671,7 @@ def process_key_event(self, e): self.incremental_search() elif (e in (("",) + key_dispatch[self.config.backspace_key]) and - self.incremental_search_mode): + self.incr_search_mode): self.add_to_incremental_search(self, backspace=True) elif e in self.edit_keys.cut_buffer_edits: self.readline_kill(e) @@ -717,7 +717,7 @@ def process_key_event(self, e): elif e in key_dispatch[self.config.edit_current_block_key]: self.send_current_block_to_external_editor() elif e in [""]: - self.incremental_search_mode = None + self.incr_search_mode = None elif e in [""]: self.add_normal_character(' ') else: @@ -735,19 +735,19 @@ def get_last_word(self): reset_rl_history=False) def incremental_search(self, reverse=False, include_current=False): - if self.incremental_search_mode is None: + if self.incr_search_mode is None: self.rl_history.enter(self.current_line) - self.incremental_search_target = '' + self.incr_search_target = '' else: - if self.incremental_search_target: + if self.incr_search_target: line = (self.rl_history.back( False, search=True, - target=self.incremental_search_target, + target=self.incr_search_target, include_current=include_current) if reverse else self.rl_history.forward( False, search=True, - target=self.incremental_search_target, + target=self.incr_search_target, include_current=include_current)) self._set_current_line(line, reset_rl_history=False, @@ -756,9 +756,9 @@ def incremental_search(self, reverse=False, include_current=False): reset_rl_history=False, clear_special_mode=False) if reverse: - self.incremental_search_mode = 'reverse_incremental_search' + self.incr_search_mode = 'reverse_incremental_search' else: - self.incremental_search_mode = 'incremental_search' + self.incr_search_mode = 'incremental_search' def readline_kill(self, e): func = self.edit_keys[e] @@ -811,7 +811,8 @@ def only_whitespace_left_of_cursor(): # 3. check to see if we can expand the current word if self.matches_iter.is_cseq(): - self._cursor_offset, self._current_line = self.matches_iter.substitute_cseq() + cursor_and_line = self.matches_iter.substitute_cseq() + self._cursor_offset, self._current_line = cursor_and_line # using _current_line so we don't trigger a completion reset if not self.matches_iter.matches: self.list_win_visible = self.complete() @@ -819,7 +820,8 @@ def only_whitespace_left_of_cursor(): elif self.matches_iter.matches: self.current_match = (back and self.matches_iter.previous() or next(self.matches_iter)) - self._cursor_offset, self._current_line = self.matches_iter.cur_line() + cursor_and_line = self.matches_iter.cur_line() + self._cursor_offset, self._current_line = cursor_and_line # using _current_line so we don't trigger a completion reset self.list_win_visible = True @@ -946,7 +948,7 @@ def toggle_file_watch(self): def add_normal_character(self, char): if len(char) > 1 or is_nop(char): return - if self.incremental_search_mode: + if self.incr_search_mode: self.add_to_incremental_search(char) else: self._set_current_line((self.current_line[:self.cursor_offset] + @@ -969,21 +971,22 @@ def add_to_incremental_search(self, char=None, backspace=False): if char is None and not backspace: raise ValueError("must provide a char or set backspace to True") if backspace: - self.incremental_search_target = self.incremental_search_target[:-1] + self.incr_search_target = self.incr_search_target[:-1] else: - self.incremental_search_target += char - if self.incremental_search_mode == 'reverse_incremental_search': + self.incr_search_target += char + if self.incr_search_mode == 'reverse_incremental_search': self.incremental_search(reverse=True, include_current=True) - elif self.incremental_search_mode == 'incremental_search': + elif self.incr_search_mode == 'incremental_search': self.incremental_search(include_current=True) else: raise ValueError('add_to_incremental_search not in a special mode') def update_completion(self, tab=False): - """Update visible docstring and matches, and possibly hide/show completion box""" + """Update visible docstring and matches and box visibility""" # Update autocomplete info; self.matches_iter and self.funcprops - # Should be called whenever the completion box might need to appear / dissapear - # when current line or cursor offset changes, unless via selecting a match + # Should be called whenever the completion box might need to appear + # or disappear; whenever current line or cursor offset changes, + # unless this happened via selecting a match self.current_match = None self.list_win_visible = BpythonRepl.complete(self, tab) @@ -995,7 +998,8 @@ def predicted_indent(self, line): indent = max(0, indent + self.config.tab_length) elif line and line.count(' ') == len(line): indent = max(0, indent - self.config.tab_length) - elif line and ':' not in line and line.strip().startswith(('return', 'pass', 'raise', 'yield')): + elif (line and ':' not in line and line.strip().startswith( + ('return', 'pass', 'raise', 'yield'))): indent = max(0, indent - self.config.tab_length) logger.debug('indent we found was %s', indent) return indent @@ -1011,10 +1015,12 @@ def push(self, line, insert_into_history=True): self.saved_indent = self.predicted_indent(line) if self.config.syntax: - display_line = bpythonparse(format(self.tokenize(line), self.formatter)) - # careful: self.tokenize requires that the line not be in self.buffer yet! + display_line = bpythonparse(format( + self.tokenize(line), self.formatter)) + # self.tokenize requires that the line not be in self.buffer yet - logger.debug('display line being pushed to buffer: %r -> %r', line, display_line) + logger.debug('display line being pushed to buffer: %r -> %r', + line, display_line) self.display_buffer.append(display_line) else: self.display_buffer.append(fmtstr(line)) @@ -1054,7 +1060,8 @@ def run_code_and_maybe_finish(self, for_code=None): # TODO This should be printed ABOVE the error that just happened # instead or maybe just thrown away and not shown if self.current_stdouterr_line: - self.display_lines.extend(paint.display_linize(self.current_stdouterr_line, self.width)) + self.display_lines.extend(paint.display_linize( + self.current_stdouterr_line, self.width)) self.current_stdouterr_line = '' self._set_current_line(' ' * indent, update_completion=True) @@ -1065,8 +1072,10 @@ def keyboard_interrupt(self): self.cursor_offset = -1 self.unhighlight_paren() self.display_lines.extend(self.display_buffer_lines) - self.display_lines.extend(paint.display_linize(self.current_cursor_line, self.width)) - self.display_lines.extend(paint.display_linize("KeyboardInterrupt", self.width)) + self.display_lines.extend(paint.display_linize( + self.current_cursor_line, self.width)) + self.display_lines.extend(paint.display_linize( + "KeyboardInterrupt", self.width)) self.clear_current_block(remove_from_history=False) def unhighlight_paren(self): @@ -1084,7 +1093,9 @@ def unhighlight_paren(self): logger.debug('trying to unhighlight a paren on line %r', lineno) logger.debug('with these tokens: %r', saved_tokens) new = bpythonparse(format(saved_tokens, self.formatter)) - self.display_buffer[lineno] = self.display_buffer[lineno].setslice_with_length(0, len(new), new, len(self.display_buffer[lineno])) + self.display_buffer[lineno] = self.display_buffer[lineno] \ + .setslice_with_length(0, len(new), new, + len(self.display_buffer[lineno])) def clear_current_block(self, remove_from_history=True): self.display_buffer = [] @@ -1105,10 +1116,12 @@ def send_to_stdout(self, output): logger.debug('display_lines: %r', self.display_lines) self.current_stdouterr_line += lines[0] if len(lines) > 1: - self.display_lines.extend(paint.display_linize(self.current_stdouterr_line, self.width, blank_line=True)) - self.display_lines.extend(sum((paint.display_linize(line, self.width, - blank_line=True) - for line in lines[1:-1]), [])) + self.display_lines.extend(paint.display_linize( + self.current_stdouterr_line, self.width, blank_line=True)) + self.display_lines.extend( + sum((paint.display_linize(line, self.width, + blank_line=True) + for line in lines[1:-1]), [])) self.current_stdouterr_line = lines[-1] logger.debug('display_lines: %r', self.display_lines) @@ -1122,7 +1135,8 @@ def send_to_stderr(self, error): def send_to_stdin(self, line): if line.endswith('\n'): - self.display_lines.extend(paint.display_linize(self.current_output_line, self.width)) + self.display_lines.extend( + paint.display_linize(self.current_output_line, self.width)) self.current_output_line = '' # formatting, output @@ -1136,13 +1150,18 @@ def done(self): def current_line_formatted(self): """The colored current line (no prompt, not wrapped)""" if self.config.syntax: - fs = bpythonparse(format(self.tokenize(self.current_line), self.formatter)) - if self.incremental_search_mode: - if self.incremental_search_target in self.current_line: - fs = fmtfuncs.on_magenta(self.incremental_search_target).join(fs.split(self.incremental_search_target)) - elif self.rl_history.saved_line and self.rl_history.saved_line in self.current_line: - if self.config.curtsies_right_arrow_completion and self.rl_history.index != 0: - fs = fmtfuncs.on_magenta(self.rl_history.saved_line).join(fs.split(self.rl_history.saved_line)) + fs = bpythonparse(format(self.tokenize(self.current_line), + self.formatter)) + if self.incr_search_mode: + if self.incr_search_target in self.current_line: + fs = fmtfuncs.on_magenta(self.incr_search_target).join( + fs.split(self.incr_search_target)) + elif (self.rl_history.saved_line and + self.rl_history.saved_line in self.current_line): + if (self.config.curtsies_right_arrow_completion and + self.rl_history.index != 0): + fs = fmtfuncs.on_magenta(self.rl_history.saved_line).join( + fs.split(self.rl_history.saved_line)) logger.debug('Display line %r -> %r', self.current_line, fs) else: fs = fmtstr(self.current_line) @@ -1158,12 +1177,13 @@ def lines_for_display(self): @property def display_buffer_lines(self): - """The display lines (wrapped, colored, with prompts) for the current buffer""" + """The display lines (wrapped, colored, +prompts) of current buffer""" lines = [] for display_line in self.display_buffer: - display_line = (func_for_letter(self.config.color_scheme['prompt_more'])(self.ps2) - if lines else - func_for_letter(self.config.color_scheme['prompt'])(self.ps1)) + display_line + prompt = func_for_letter(self.config.color_scheme['prompt']) + more = func_for_letter(self.config.color_scheme['prompt_more']) + display_line = ((more(self.ps2) if lines else prompt(self.ps1)) + + display_line) for line in paint.display_linize(display_line, self.width): lines.append(line) return lines @@ -1171,15 +1191,16 @@ def display_buffer_lines(self): @property def display_line_with_prompt(self): """colored line with prompt""" - if self.incremental_search_mode == 'reverse_incremental_search': - return func_for_letter(self.config.color_scheme['prompt'])( - '(reverse-i-search)`%s\': ' % (self.incremental_search_target,)) + self.current_line_formatted - elif self.incremental_search_mode == 'incremental_search': - return func_for_letter(self.config.color_scheme['prompt'])( - '(i-search)`%s\': ' % (self.incremental_search_target,)) + self.current_line_formatted - return (func_for_letter(self.config.color_scheme['prompt'])(self.ps1) - if self.done else - func_for_letter(self.config.color_scheme['prompt_more'])(self.ps2)) + self.current_line_formatted + prompt = func_for_letter(self.config.color_scheme['prompt']) + more = func_for_letter(self.config.color_scheme['prompt_more']) + if self.incr_search_mode == 'reverse_incremental_search': + return (prompt('(reverse-i-search)`{}\': '.format( + self.incr_search_target)) + self.current_line_formatted) + elif self.incr_search_mode == 'incremental_search': + return (prompt('(i-search)`%s\': '.format( + self.incr_search_target)) + self.current_line_formatted) + return ((prompt(self.ps1) if self.done else more(self.ps2)) + + self.current_line_formatted) @property def current_cursor_line_without_suggestion(self): @@ -1193,8 +1214,10 @@ def current_cursor_line_without_suggestion(self): @property def current_cursor_line(self): if self.config.curtsies_right_arrow_completion: + suggest = func_for_letter( + self.config.color_scheme['right_arrow_suggestion']) return (self.current_cursor_line_without_suggestion + - func_for_letter(self.config.color_scheme['right_arrow_suggestion'])(self.current_suggestion)) + suggest(self.current_suggestion)) else: return self.current_cursor_line_without_suggestion @@ -1227,25 +1250,29 @@ def paint(self, about_to_exit=False, user_quit=False): """ # The hairiest function in the curtsies - a cleanup would be great. if about_to_exit: - self.clean_up_current_line_for_exit() # exception to not changing state! + # exception to not changing state! + self.clean_up_current_line_for_exit() width, min_height = self.width, self.height - show_status_bar = ((bool(self.status_bar.should_show_message) or self.status_bar.has_focus) - and not self.request_paint_to_pad_bottom) + show_status_bar = ((bool(self.status_bar.should_show_message) or + self.status_bar.has_focus) and + not self.request_paint_to_pad_bottom) if show_status_bar: # because we're going to tack the status bar on at the end, shoot # for an array one less than the height of the screen min_height -= 1 - current_line_start_row = len(self.lines_for_display) - max(0, self.scroll_offset) + current_line_start_row = (len(self.lines_for_display) - + max(0, self.scroll_offset)) # TODO how is the situation of self.scroll_offset < 0 possible? - # current_line_start_row = len(self.lines_for_display) - self.scroll_offset - if self.request_paint_to_clear_screen: # or show_status_bar and about_to_exit ? + # or show_status_bar and about_to_exit ? + if self.request_paint_to_clear_screen: self.request_paint_to_clear_screen = False arr = FSArray(min_height + current_line_start_row, width) elif self.request_paint_to_pad_bottom: # min_height - 1 for startup banner with python version - arr = FSArray(min(self.request_paint_to_pad_bottom, min_height - 1), width) + height = min(self.request_paint_to_pad_bottom, min_height - 1) + arr = FSArray(height, width) self.request_paint_to_pad_bottom = 0 else: arr = FSArray(0, width) @@ -1265,7 +1292,8 @@ def move_screen_up(current_line_start_row): 'was %s', self.scroll_offset, current_line_start_row) self.scroll_offset = self.scroll_offset - self.height - current_line_start_row = len(self.lines_for_display) - max(-1, self.scroll_offset) + current_line_start_row = (len(self.lines_for_display) - + max(-1, self.scroll_offset)) logger.debug('scroll_offset changed to %s, ' 'current_line_start_row changed to %s', self.scroll_offset, current_line_start_row) @@ -1290,9 +1318,11 @@ def move_screen_up(current_line_start_row): arr[1:history.height + 1, :history.width] = history if arr.height <= min_height: - arr[min_height, 0] = ' ' # force scroll down to hide broken history message + # force scroll down to hide broken history message + arr[min_height, 0] = ' ' - elif current_line_start_row < 0: # if current line trying to be drawn off the top of the screen + elif current_line_start_row < 0: + # if current line trying to be drawn off the top of the screen logger.debug(CONTIGUITY_BROKEN_MSG) msg = CONTIGUITY_BROKEN_MSG arr[0, 0:min(len(msg), width)] = [msg[:width]] @@ -1317,11 +1347,14 @@ def move_screen_up(current_line_start_row): self.inconsistent_history = False if user_quit: # quit() or exit() in interp - current_line_start_row = current_line_start_row - current_line.height - logger.debug("---current line row slice %r, %r", current_line_start_row, + current_line_start_row = (current_line_start_row - + current_line.height) + logger.debug("---current line row slice %r, %r", + current_line_start_row, current_line_start_row + current_line.height) logger.debug("---current line col slice %r, %r", 0, current_line.width) - arr[current_line_start_row:current_line_start_row + current_line.height, + arr[current_line_start_row:(current_line_start_row + + current_line.height), 0:current_line.width] = current_line if current_line.height > min_height: @@ -1332,62 +1365,89 @@ def move_screen_up(current_line_start_row): current_line_end_row = current_line_start_row + len(lines) - 1 if self.stdin.has_focus: - cursor_row, cursor_column = divmod(len(self.current_stdouterr_line) + self.stdin.cursor_offset, width) + cursor_row, cursor_column = divmod( + len(self.current_stdouterr_line) + + self.stdin.cursor_offset, width) assert cursor_column >= 0, cursor_column elif self.coderunner.running: # TODO does this ever happen? - cursor_row, cursor_column = divmod(len(self.current_cursor_line_without_suggestion) + self.cursor_offset, width) - assert cursor_column >= 0, (cursor_column, len(self.current_cursor_line), len(self.current_line), self.cursor_offset) + cursor_row, cursor_column = divmod( + (len(self.current_cursor_line_without_suggestion) + + self.cursor_offset), + width) + assert (cursor_column >= 0, + (cursor_column, len(self.current_cursor_line), + len(self.current_line), self.cursor_offset)) else: - cursor_row, cursor_column = divmod(len(self.current_cursor_line_without_suggestion) - len(self.current_line) + self.cursor_offset, width) - assert cursor_column >= 0, (cursor_column, len(self.current_cursor_line), len(self.current_line), self.cursor_offset) + cursor_row, cursor_column = divmod( + (len(self.current_cursor_line_without_suggestion) - + len(self.current_line) + self.cursor_offset), + width) + assert (cursor_column >= 0, + (cursor_column, len(self.current_cursor_line), + len(self.current_line), self.cursor_offset)) cursor_row += current_line_start_row if self.list_win_visible and not self.coderunner.running: logger.debug('infobox display code running') visible_space_above = history.height potential_space_below = min_height - current_line_end_row - 1 - visible_space_below = potential_space_below - self.get_top_usable_line() + visible_space_below = (potential_space_below - + self.get_top_usable_line()) if self.config.curtsies_list_above: info_max_rows = max(visible_space_above, visible_space_below) else: - minimum_possible_height = 30 # smallest an over-full completion box - info_max_rows = max(visible_space_below, minimum_possible_height) - infobox = paint.paint_infobox(info_max_rows, - int(width * self.config.cli_suggestion_width), - self.matches_iter.matches, - self.funcprops, - self.arg_pos, - self.current_match, - self.docstring, - self.config, - self.matches_iter.completer.format - if self.matches_iter.completer - else None) - - if visible_space_below >= infobox.height or not self.config.curtsies_list_above: - arr[current_line_end_row + 1:current_line_end_row + 1 + infobox.height, 0:infobox.width] = infobox + # smallest allowed over-full completion box + minimum_possible_height = 30 + info_max_rows = max(visible_space_below, + minimum_possible_height) + infobox = paint.paint_infobox( + info_max_rows, + int(width * self.config.cli_suggestion_width), + self.matches_iter.matches, + self.funcprops, + self.arg_pos, + self.current_match, + self.docstring, + self.config, + self.matches_iter.completer.format + if self.matches_iter.completer else None) + + if (visible_space_below >= infobox.height or + not self.config.curtsies_list_above): + arr[current_line_end_row + 1:(current_line_end_row + 1 + + infobox.height), + 0:infobox.width] = infobox else: - arr[current_line_start_row - infobox.height:current_line_start_row, 0:infobox.width] = infobox - logger.debug('slamming infobox of shape %r into arr of shape %r', infobox.shape, arr.shape) + arr[current_line_start_row - infobox.height: + current_line_start_row, 0:infobox.width] = infobox + logger.debug('infobox of shape %r added to arr of shape %r', + infobox.shape, arr.shape) logger.debug('about to exit: %r', about_to_exit) if show_status_bar: - statusbar_row = min_height if arr.height == min_height else arr.height + statusbar_row = (min_height + if arr.height == min_height else arr.height) if about_to_exit: arr[statusbar_row, :] = FSArray(1, width) else: - arr[statusbar_row, :] = paint.paint_statusbar(1, width, self.status_bar.current_line, self.config) + arr[statusbar_row, :] = paint.paint_statusbar( + 1, width, self.status_bar.current_line, self.config) if self.presentation_mode: rows = arr.height columns = arr.width - last_key_box = paint.paint_last_events(rows, columns, [events.pp_event(x) for x in self.last_events if x], self.config) - arr[arr.height-last_key_box.height:arr.height, arr.width-last_key_box.width:arr.width] = last_key_box + last_key_box = paint.paint_last_events( + rows, columns, + [events.pp_event(x) for x in self.last_events if x], + self.config) + arr[arr.height-last_key_box.height:arr.height, + arr.width-last_key_box.width:arr.width] = last_key_box if self.config.color_scheme['background'] not in ('d', 'D'): for r in range(arr.height): - arr[r] = fmtstr(arr[r], bg=color_for_letter(self.config.color_scheme['background'])) + bg = color_for_letter(self.config.color_scheme['background']) + arr[r] = fmtstr(arr[r], bg=bg) logger.debug('returning arr of size %r', arr.shape) logger.debug('cursor pos: %r', (cursor_row, cursor_column)) return arr, (cursor_row, cursor_column) @@ -1444,7 +1504,7 @@ def _set_cursor_offset(self, offset, update_completion=True, if reset_rl_history: self.rl_history.reset() if clear_special_mode: - self.incremental_search_mode = None + self.incr_search_mode = None self._cursor_offset = offset if update_completion: self.update_completion() @@ -1472,7 +1532,8 @@ def cpos(self): def reprint_line(self, lineno, tokens): logger.debug("calling reprint line with %r %r", lineno, tokens) if self.config.syntax: - self.display_buffer[lineno] = bpythonparse(format(tokens, self.formatter)) + self.display_buffer[lineno] = bpythonparse( + format(tokens, self.formatter)) def take_back_buffer_line(self): assert len(self.buffer) > 0 @@ -1529,15 +1590,22 @@ def reevaluate(self, insert_into_history=False): sys.stdin = self.stdin self.reevaluating = False - num_lines_onscreen = len(self.lines_for_display) - max(0, self.scroll_offset) - display_lines_offscreen = self.display_lines[:len(self.display_lines) - num_lines_onscreen] - old_display_lines_offscreen = old_display_lines[:len(self.display_lines) - num_lines_onscreen] - logger.debug('old_display_lines_offscreen %s', '|'.join(str(x) for x in old_display_lines_offscreen)) - logger.debug(' display_lines_offscreen %s', '|'.join(str(x) for x in display_lines_offscreen)) - if old_display_lines_offscreen[:len(display_lines_offscreen)] != display_lines_offscreen and not self.history_already_messed_up: - # self.scroll_offset = self.scroll_offset + (len(old_display_lines)-len(self.display_lines)) + num_lines_onscreen = (len(self.lines_for_display) - + max(0, self.scroll_offset)) + display_lines_offscreen = self.display_lines[:len(self.display_lines) - + num_lines_onscreen] + old_display_lines_offscreen = old_display_lines[:( + len(self.display_lines) - num_lines_onscreen)] + logger.debug('old_display_lines_offscreen %s', '|'.join( + str(x) for x in old_display_lines_offscreen)) + logger.debug(' display_lines_offscreen %s', '|'.join( + str(x) for x in display_lines_offscreen)) + if ((old_display_lines_offscreen[:len(display_lines_offscreen)] != + display_lines_offscreen) and + not self.history_already_messed_up): self.inconsistent_history = True - logger.debug('after rewind, self.inconsistent_history is %r', self.inconsistent_history) + logger.debug('after rewind, self.inconsistent_history is %r', + self.inconsistent_history) self._cursor_offset = 0 self.current_line = '' @@ -1603,9 +1671,11 @@ def key_help_text(self): NOT_IMPLEMENTED = ('suspend', 'cut to buffer', 'search', 'last output', 'yank from buffer', 'cut to buffer') pairs = [] - pairs.append(['complete history suggestion', 'right arrow at end of line']) + pairs.append(['complete history suggestion', + 'right arrow at end of line']) pairs.append(['previous match with current line', 'up arrow']) - for functionality, key in [(attr[:-4].replace('_', ' '), getattr(self.config, attr)) + for functionality, key in [(attr[:-4].replace('_', ' '), + getattr(self.config, attr)) for attr in self.config.__dict__ if attr.endswith('key')]: if functionality in NOT_IMPLEMENTED: @@ -1616,7 +1686,8 @@ def key_help_text(self): pairs.append([functionality, key]) max_func = max(len(func) for func, key in pairs) - return '\n'.join('%s : %s' % (func.rjust(max_func), key) for func, key in pairs) + return '\n'.join('%s : %s' % (func.rjust(max_func), key) + for func, key in pairs) def is_nop(char): From 2fe8fc485ae289787ec35b5ecb4445671759fa80 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 15 Dec 2015 11:02:55 -0500 Subject: [PATCH 0684/1650] fix assert syntax --- bpython/curtsiesfrontend/repl.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 55ecac54c..306afb398 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1374,17 +1374,17 @@ def move_screen_up(current_line_start_row): (len(self.current_cursor_line_without_suggestion) + self.cursor_offset), width) - assert (cursor_column >= 0, - (cursor_column, len(self.current_cursor_line), - len(self.current_line), self.cursor_offset)) + assert cursor_column >= 0, ( + cursor_column, len(self.current_cursor_line), + len(self.current_line), self.cursor_offset) else: cursor_row, cursor_column = divmod( (len(self.current_cursor_line_without_suggestion) - len(self.current_line) + self.cursor_offset), width) - assert (cursor_column >= 0, - (cursor_column, len(self.current_cursor_line), - len(self.current_line), self.cursor_offset)) + assert cursor_column >= 0, ( + cursor_column, len(self.current_cursor_line), + len(self.current_line), self.cursor_offset) cursor_row += current_line_start_row if self.list_win_visible and not self.coderunner.running: From 49558fed7b509f42c456d00a93d129d9bd1da02e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 15 Dec 2015 11:07:54 -0500 Subject: [PATCH 0685/1650] PEP8 preprocess.py --- bpython/curtsiesfrontend/preprocess.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index 0ecea6621..473a0c346 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -36,7 +36,9 @@ def indent_empty_lines(s, compiler): def leading_tabs_to_spaces(s): lines = s.split('\n') result_lines = [] - tab_to_space = lambda m: len(m.group()) * 4 * ' ' + + def tab_to_space(m): + return len(m.group()) * 4 * ' ' for line in lines: result_lines.append(tabs_to_spaces_re.sub(tab_to_space, line)) return '\n'.join(result_lines) From 39911aca0b986c194b8c3610c0ff5f6db9559044 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 15 Dec 2015 11:10:14 -0500 Subject: [PATCH 0686/1650] PEP8 curtsies.py --- bpython/curtsies.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index df33c816f..45981c260 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -50,7 +50,7 @@ def __init__(self, config, locals_, banner, interp=None, self._request_reload = self.input_generator.threadsafe_event_trigger( bpythonevents.ReloadEvent) self.interrupting_refresh = (self.input_generator - .threadsafe_event_trigger(lambda: None)) + .threadsafe_event_trigger(lambda: None)) self.request_undo = self.input_generator.event_trigger( bpythonevents.UndoEvent) @@ -58,11 +58,11 @@ def __init__(self, config, locals_, banner, interp=None, pass # temp hack to get .original_stty BaseRepl.__init__(self, - locals_=locals_, - config=config, - banner=banner, - interp=interp, - orig_tcattrs=self.input_generator.original_stty) + locals_=locals_, + config=config, + banner=banner, + interp=interp, + orig_tcattrs=self.input_generator.original_stty) def get_term_hw(self): return self.window.get_term_hw() @@ -132,6 +132,7 @@ def mainloop(self, interactive=True, paste=None): for e in inputs: self.process_event(e) + def main(args=None, locals_=None, banner=None, welcome_message=None): """ banner is displayed directly after the version information. From ef69e4e4745d00e3d281e61b37b1950c4aae512e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 15 Dec 2015 11:11:19 -0500 Subject: [PATCH 0687/1650] remove dead code from class refactor --- bpython/curtsies.py | 106 -------------------------------------------- 1 file changed, 106 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 45981c260..b6c67df17 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -235,111 +235,5 @@ def combined_events(event_provider, paste_threshold=3): return g -def mainloop(config, locals_, banner, interp=None, paste=None, - interactive=True): - with curtsies.input.Input(keynames='curtsies', - sigint_event=True, - paste_threshold=None) as input_generator: - with curtsies.window.CursorAwareWindow( - sys.stdout, - sys.stdin, - keep_last_line=True, - hide_cursor=False, - extra_bytes_callback=input_generator.unget_bytes) as window: - - request_refresh = input_generator.event_trigger( - bpythonevents.RefreshRequestEvent) - schedule_refresh = input_generator.scheduled_event_trigger( - bpythonevents.ScheduledRefreshRequestEvent) - request_reload = input_generator.threadsafe_event_trigger( - bpythonevents.ReloadEvent) - interrupting_refresh = input_generator.threadsafe_event_trigger( - lambda: None) - request_undo = input_generator.event_trigger( - bpythonevents.UndoEvent) - - def on_suspend(): - window.__exit__(None, None, None) - input_generator.__exit__(None, None, None) - - def after_suspend(): - input_generator.__enter__() - window.__enter__() - interrupting_refresh() - - def get_top_usable_line(): - return window.top_usable_row - - # global for easy introspection `from bpython.curtsies import repl` - global repl - with Repl(config=config, - locals_=locals_, - request_refresh=request_refresh, - schedule_refresh=schedule_refresh, - request_reload=request_reload, - request_undo=request_undo, - get_term_hw=window.get_term_hw, - get_cursor_vertical_diff=window.get_cursor_vertical_diff, - banner=banner, - interp=interp, - interactive=interactive, - orig_tcattrs=input_generator.original_stty, - on_suspend=on_suspend, - after_suspend=after_suspend, - get_top_usable_line=get_top_usable_line) as repl: - repl.height, repl.width = window.t.height, window.t.width - - repl.request_paint_to_pad_bottom = 6 - - def process_event(e): - """If None is passed in, just paint the screen""" - try: - if e is not None: - repl.process_event(e) - except (SystemExitFromCodeGreenlet, SystemExit) as err: - array, cursor_pos = repl.paint( - about_to_exit=True, - user_quit=isinstance(err, - SystemExitFromCodeGreenlet)) - scrolled = window.render_to_terminal(array, cursor_pos) - repl.scroll_offset += scrolled - raise - else: - array, cursor_pos = repl.paint() - scrolled = window.render_to_terminal(array, cursor_pos) - repl.scroll_offset += scrolled - - if interactive: - # Add custom help command - # TODO: add methods to run the code - repl.coderunner.interp.locals['_repl'] = repl - - repl.coderunner.interp.runsource( - 'from bpython.curtsiesfrontend._internal ' - 'import _Helper') - repl.coderunner.interp.runsource('help = _Helper(_repl)\n') - - del repl.coderunner.interp.locals['_repl'] - del repl.coderunner.interp.locals['_Helper'] - - # run startup file - process_event(bpythonevents.RunStartupFileEvent()) - - # handle paste - if paste: - process_event(paste) - - # do a display before waiting for first event - process_event(None) - inputs = combined_events(input_generator) - for unused in find_iterator: - e = inputs.send(0) - if e is not None: - process_event(e) - - for e in inputs: - process_event(e) - - if __name__ == '__main__': sys.exit(main()) From 6567f4c8eafddc4a13de06aaa9176452a76ad64b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 15 Dec 2015 11:13:55 -0500 Subject: [PATCH 0688/1650] formatting fix in simplerepl --- bpython/simplerepl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/simplerepl.py b/bpython/simplerepl.py index cb85b4fa2..4038cefdf 100644 --- a/bpython/simplerepl.py +++ b/bpython/simplerepl.py @@ -21,12 +21,12 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - -from __future__ import unicode_literals, print_function, absolute_import """An example bpython repl without a nice UI for testing and to demonstrate the methods of bpython.curtsiesrepl.repl.BaseRepl that must be overridden. """ +from __future__ import unicode_literals, print_function, absolute_import + import time import logging From 31e785ad102540c2329b4cf491fa8b8ff0f016a8 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 16 Dec 2015 13:45:42 -0500 Subject: [PATCH 0689/1650] fix redraw issues Inadvertent method override of process_event caused rewinds to render progressively causing unsmooth rewinds and rewinds that rewrote the screen unnecessarily. --- bpython/curtsies.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index b6c67df17..7f47d8145 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -82,11 +82,11 @@ def after_suspend(self): self.window.__enter__() self.interrupting_refresh() - def process_event(self, e): + def process_event_and_paint(self, e): """If None is passed in, just paint the screen""" try: if e is not None: - BaseRepl.process_event(self, e) + self.process_event(e) except (SystemExitFromCodeGreenlet, SystemExit) as err: array, cursor_pos = self.paint( about_to_exit=True, @@ -122,15 +122,15 @@ def mainloop(self, interactive=True, paste=None): self.process_event(paste) # do a display before waiting for first event - self.process_event(None) + self.process_event_and_paint(None) inputs = combined_events(self.input_generator) for unused in find_iterator: e = inputs.send(0) if e is not None: - self.process_event(e) + self.process_event_and_paint(e) for e in inputs: - self.process_event(e) + self.process_event_and_paint(e) def main(args=None, locals_=None, banner=None, welcome_message=None): From e73c30ccb28301372e820cc4edfdf6b1262dc07e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 16 Dec 2015 17:39:46 -0500 Subject: [PATCH 0690/1650] pep8 simpleeval --- bpython/simpleeval.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index e27801be6..b8931a07f 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -44,6 +44,7 @@ else: _name_type_nodes = (ast.Name,) + class EvaluationError(Exception): """Raised if an exception occurred in safe_eval.""" @@ -113,9 +114,9 @@ def _convert(node): raise EvaluationError("can't lookup %s" % node.id) # unary + and - are allowed on any type - elif isinstance(node, ast.UnaryOp) and \ - isinstance(node.op, (ast.UAdd, ast.USub)): - # ast.literal_eval does ast typechecks here, we use type checks + elif (isinstance(node, ast.UnaryOp) and + isinstance(node.op, (ast.UAdd, ast.USub))): + # ast.literal_eval does ast typechecks here, we use type checks operand = _convert(node.operand) if not type(operand) in _numeric_types: raise ValueError("unary + and - only allowed on builtin nums") @@ -123,12 +124,13 @@ def _convert(node): return + operand else: return - operand - elif isinstance(node, ast.BinOp) and \ - isinstance(node.op, (ast.Add, ast.Sub)): + elif (isinstance(node, ast.BinOp) and + isinstance(node.op, (ast.Add, ast.Sub))): # ast.literal_eval does ast typechecks here, we use type checks left = _convert(node.left) right = _convert(node.right) - if not (type(left) in _numeric_types and type(right) in _numeric_types): + if not (type(left) in _numeric_types and + type(right) in _numeric_types): raise ValueError("binary + and - only allowed on builtin nums") if isinstance(node.op, ast.Add): return left + right @@ -136,8 +138,8 @@ def _convert(node): return left - right # this is a deviation from literal_eval: we allow indexing - elif isinstance(node, ast.Subscript) and \ - isinstance(node.slice, ast.Index): + elif (isinstance(node, ast.Subscript) and + isinstance(node.slice, ast.Index)): obj = _convert(node.value) index = _convert(node.slice.value) return safe_getitem(obj, index) @@ -187,7 +189,7 @@ def evaluate_current_expression(cursor_offset, line, namespace=None): attr_before_cursor = temp_line[temp_attribute.start:temp_cursor] def parse_trees(cursor_offset, line): - for i in range(cursor_offset-1, -1, -1): + for i in range(cursor_offset - 1, -1, -1): try: tree = ast.parse(line[i:cursor_offset]) yield tree @@ -201,7 +203,8 @@ def parse_trees(cursor_offset, line): largest_ast = attribute_access.value if largest_ast is None: - raise EvaluationError("Corresponding ASTs to right of cursor are invalid") + raise EvaluationError( + "Corresponding ASTs to right of cursor are invalid") try: return simple_eval(largest_ast, namespace) except ValueError: @@ -209,7 +212,7 @@ def parse_trees(cursor_offset, line): def evaluate_current_attribute(cursor_offset, line, namespace=None): - """Safely evaluates the expression attribute lookup currently occuring on""" + """Safely evaluates the expression having an attributed accesssed""" # this function runs user code in case of custom descriptors, # so could fail in any way @@ -220,4 +223,5 @@ def evaluate_current_attribute(cursor_offset, line, namespace=None): try: return getattr(obj, attr.word) except AttributeError: - raise EvaluationError("can't lookup attribute %s on %r" % (attr.word, obj)) + raise EvaluationError( + "can't lookup attribute %s on %r" % (attr.word, obj)) From 2418acf683110262efaf9e794b3f7d168825044f Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 16 Dec 2015 17:42:09 -0500 Subject: [PATCH 0691/1650] pep8 autocomplete.py --- bpython/autocomplete.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 97c2ef179..fd4c2facb 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -74,6 +74,7 @@ def after_last_dot(name): return name.rstrip('.').rsplit('.')[-1] + def few_enough_underscores(current, match): """Returns whether match should be shown based on current @@ -139,8 +140,9 @@ def matches(self, cursor_offset, line, **kwargs): def locate(self, cursor_offset, line): """Returns a Linepart namedtuple instance or None given cursor and line - A Linepart namedtuple contains a start, stop, and word. None is returned - if no target for this type of completion is found under the cursor.""" + A Linepart namedtuple contains a start, stop, and word. None is + returned if no target for this type of completion is found under + the cursor.""" raise NotImplementedError def format(self, word): @@ -240,7 +242,7 @@ def locate(self, current_offset, line): def format(self, filename): filename.rstrip(os.sep).rsplit(os.sep)[-1] if os.sep in filename[:-1]: - return filename[filename.rindex(os.sep, 0, -1)+1:] + return filename[filename.rindex(os.sep, 0, -1) + 1:] else: return filename @@ -474,12 +476,10 @@ def matches(self, cursor_offset, line, **kwargs): except EvaluationError: return set() with inspection.AttrCleaner(obj): - # strips leading dot + # strips leading dot matches = [m[1:] for m in self.attr_lookup(obj, '', attr.word)] - return set(m for m in matches if few_enough_underscores(attr.word, m)) - return matches try: @@ -517,7 +517,7 @@ def matches(self, cursor_offset, line, **kwargs): self._orig_start = None return None - first_letter = line[self._orig_start:self._orig_start+1] + first_letter = line[self._orig_start:self._orig_start + 1] matches = [c.name for c in completions] if any(not m.lower().startswith(matches[0][0].lower()) From 01bda16f861c596dc91c34571ebfd9163970ec5a Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 16 Dec 2015 18:09:36 -0500 Subject: [PATCH 0692/1650] PEP8 --- bpython/inspection.py | 12 ++++++------ bpython/line.py | 9 +++++---- bpython/paste.py | 6 +++--- bpython/repl.py | 19 +++++++++++-------- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index c4f05c086..a66a04e79 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -41,7 +41,7 @@ _name = LazyReCompile(r'[a-zA-Z_]\w*$') -ArgSpec = namedtuple('ArgSpec', ['args', 'varargs', 'varkwargs', 'defaults', +ArgSpec = namedtuple('ArgSpec', ['args', 'varargs', 'varkwargs', 'defaults', 'kwonly', 'kwonly_defaults', 'annotations']) FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method']) @@ -229,11 +229,11 @@ def getfuncprops(func, f): func_name = None try: - is_bound_method = ((inspect.ismethod(f) and f.__self__ is not None) - or (func_name == '__init__' and not - func.endswith('.__init__')) - or (func_name == '__new__' and not - func.endswith('.__new__'))) + is_bound_method = ((inspect.ismethod(f) and f.__self__ is not None) or + (func_name == '__init__' and not + func.endswith('.__init__')) or + (func_name == '__new__' and not + func.endswith('.__new__'))) except: # if f is a method from a xmlrpclib.Server instance, func_name == # '__init__' throws xmlrpclib.Fault (see #202) diff --git a/bpython/line.py b/bpython/line.py index 996f850db..60388b88e 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -92,7 +92,7 @@ def current_object(cursor_offset, line): s += m.group(1) if not s: return None - return LinePart(start, start+len(s), s) + return LinePart(start, start + len(s), s) current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]?') @@ -100,7 +100,7 @@ def current_object(cursor_offset, line): def current_object_attribute(cursor_offset, line): """If in attribute completion, the attribute being completed""" - #TODO replace with more general current_expression_attribute + # TODO replace with more general current_expression_attribute match = current_word(cursor_offset, line) if match is None: return None @@ -216,12 +216,13 @@ def current_dotted_attribute(cursor_offset, line): return LinePart(start, end, word) -current_expression_attribute_re = LazyReCompile(r'[.]\s*((?:[\w_][\w0-9_]*)|(?:))') +current_expression_attribute_re = LazyReCompile( + r'[.]\s*((?:[\w_][\w0-9_]*)|(?:))') def current_expression_attribute(cursor_offset, line): """If after a dot, the attribute being completed""" - #TODO replace with more general current_expression_attribute + # TODO replace with more general current_expression_attribute matches = current_expression_attribute_re.finditer(line) for m in matches: if (m.start(1) <= cursor_offset and m.end(1) >= cursor_offset): diff --git a/bpython/paste.py b/bpython/paste.py index 237807287..efa383b5b 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -103,9 +103,9 @@ def paste(self, s): raise PasteFailed(_('No output from helper program.')) else: parsed_url = urlparse(paste_url) - if (not parsed_url.scheme - or any(unicodedata.category(c) == 'Cc' - for c in paste_url)): + if (not parsed_url.scheme or + any(unicodedata.category(c) == 'Cc' + for c in paste_url)): raise PasteFailed(_('Failed to recognize the helper ' 'program\'s output as an URL.')) diff --git a/bpython/repl.py b/bpython/repl.py index ea87bcc0c..b93f3368d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -539,14 +539,16 @@ def get_args(self): class_f = None if (hasattr(f, '__init__') and - f.__init__ is not object.__init__): - class_f = f.__init__ + f.__init__ is not object.__init__): + class_f = f.__init__ if ((not class_f or - not inspection.getfuncprops(func, class_f)) and - hasattr(f, '__new__') and - f.__new__ is not object.__new__ and - f.__new__.__class__ is not object.__new__.__class__): # py3 - class_f = f.__new__ + not inspection.getfuncprops(func, class_f)) and + hasattr(f, '__new__') and + f.__new__ is not object.__new__ and + # py3 + f.__new__.__class__ is not object.__new__.__class__): + + class_f = f.__new__ if class_f: f = class_f @@ -683,7 +685,8 @@ def next_indentation(self): indentation = next_indentation(self.buffer[-1], self.config.tab_length) if indentation and self.config.dedent_after > 0: - line_is_empty = lambda line: not line.strip() + def line_is_empty(line): + return not line.strip() empty_lines = takewhile(line_is_empty, reversed(self.buffer)) if sum(1 for _ in empty_lines) >= self.config.dedent_after: indentation -= 1 From 4cd5453c3a8f8d6ae0118d5d0fa11d829017f212 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 16 Dec 2015 18:32:28 -0500 Subject: [PATCH 0693/1650] pep8 tests --- bpython/test/test_autocomplete.py | 11 +++++--- bpython/test/test_curtsies_painting.py | 9 ++++--- bpython/test/test_curtsies_repl.py | 31 +++++++++++----------- bpython/test/test_interpreter.py | 25 +++++++++++------- bpython/test/test_manual_readline.py | 36 ++++++++++++++++---------- bpython/test/test_repl.py | 3 ++- bpython/test/test_simpleeval.py | 13 +++++----- 7 files changed, 74 insertions(+), 54 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 119fdd3dd..87fda85a4 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -250,7 +250,8 @@ def test_att_matches_found_on_old_style_instance(self): locals_={'a': OldStyleFoo()}), set(['a.method', 'a.a', 'a.b'])) self.assertIn(u'a.__dict__', - self.com.matches(4, 'a.__', locals_={'a': OldStyleFoo()})) + self.com.matches(4, 'a.__', + locals_={'a': OldStyleFoo()})) @skip_old_style def test_att_matches_found_on_old_style_class_object(self): @@ -274,7 +275,8 @@ def setUpClass(cls): cls.com = autocomplete.ExpressionAttributeCompletion() def test_att_matches_found_on_instance(self): - self.assertSetEqual(self.com.matches(5, 'a[0].', locals_={'a': [Foo()]}), + self.assertSetEqual(self.com.matches(5, 'a[0].', + locals_={'a': [Foo()]}), set(['method', 'a', 'b'])) @skip_old_style @@ -297,7 +299,8 @@ def test_tuples_complete(self): @unittest.skip('TODO, subclasses do not complete yet') def test_list_subclasses_complete(self): - class ListSubclass(list): pass + class ListSubclass(list): + pass self.assertSetEqual(self.com.matches(5, 'a[0].', locals_={'a': ListSubclass([Foo()])}), set(['method', 'a', 'b'])) @@ -394,7 +397,7 @@ def test_completions_are_unicode(self): @unittest.skipIf(py3, "in Python 3 invalid identifiers are passed through") def test_ignores_nonascii_encodable(self): self.assertEqual(self.com.matches(3, 'abc', locals_={'abcß': 10}), - None) + None) def test_mock_kwlist(self): with mock.patch.object(keyword, 'kwlist', new=['abcd']): diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 55174b908..df3507369 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -185,6 +185,7 @@ def undo(self): def setUp(self): self.refresh_requests = [] + class TestRepl(BaseRepl): def _request_refresh(inner_self): self.refresh() @@ -553,14 +554,14 @@ def test_unhighlight_paren_bugs(self): self.assertEqual(self.repl.rl_history.entries, ['']) self.repl.process_event(')') self.assertEqual(self.repl.rl_history.entries, ['']) - screen = fsarray([cyan(">>> ")+on_magenta(bold(red('('))), - green("... ")+on_magenta(bold(red(')')))]) + screen = fsarray([cyan(">>> ") + on_magenta(bold(red('('))), + green("... ") + on_magenta(bold(red(')')))]) self.assert_paint(screen, (1, 5)) with output_to_repl(self.repl): self.repl.process_event(' ') - screen = fsarray([cyan(">>> ")+yellow('('), - green("... ")+yellow(')')+bold(cyan(" "))]) + screen = fsarray([cyan(">>> ") + yellow('('), + green("... ") + yellow(')') + bold(cyan(" "))]) self.assert_paint(screen, (1, 6)) def send_key(self, key): diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 58c0b84ca..d9f278069 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -18,7 +18,7 @@ from bpython import args from bpython._py3compat import py3 from bpython.test import (FixLanguageTestCase as TestCase, MagicIterMock, mock, - builtin_target, unittest) + unittest) from curtsies import events @@ -299,8 +299,8 @@ def setUp(self): self.open = partial(io.open, mode='wt', encoding='utf-8') self.dont_write_bytecode = sys.dont_write_bytecode sys.dont_write_bytecode = True - self.sys_path = sys.path #? - sys.path = self.sys_path[:] #? + self.sys_path = sys.path + sys.path = self.sys_path[:] # Because these tests create Python source files at runtime, # it's possible in Python >=3.3 for the importlib.machinery.FileFinder @@ -318,7 +318,7 @@ def setUp(self): def tearDown(self): sys.dont_write_bytecode = self.dont_write_bytecode - sys.path = self.sys_path #? + sys.path = self.sys_path def push(self, line): self.repl._current_line = line @@ -358,21 +358,21 @@ def test_import_module_with_rewind(self): with self.open(fullpath) as f: f.write('a = 0\n') self.head(path) - self.push('import %s' % (modname)) # SOMETIMES THIS MAKES THE OTHER TEST FAIL!!! - #self.assertIn(modname, self.repl.interp.locals) + self.push('import %s' % (modname)) + self.assertIn(modname, self.repl.interp.locals) self.repl.undo() - #self.assertNotIn(modname, self.repl.interp.locals) + self.assertNotIn(modname, self.repl.interp.locals) self.repl.clear_modules_and_reevaluate() - #self.assertNotIn(modname, self.repl.interp.locals) - #self.push('import %s' % (modname)) - #self.push('a = %s.a' % (modname)) - #self.assertIn('a', self.repl.interp.locals) - #self.assertEqual(self.repl.interp.locals['a'], 0) + self.assertNotIn(modname, self.repl.interp.locals) + self.push('import %s' % (modname)) + self.push('a = %s.a' % (modname)) + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 0) with self.open(fullpath) as f: f.write('a = 1\n') - #self.repl.clear_modules_and_reevaluate() - #self.assertIn('a', self.repl.interp.locals) - #self.assertEqual(self.repl.interp.locals['a'], 1) + self.repl.clear_modules_and_reevaluate() + self.assertIn('a', self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals['a'], 1) class TestCurtsiesPagerText(TestCase): @@ -443,7 +443,6 @@ def test_control_events_in_small_paste(self): self.repl.process_event(p) self.assertEqual(self.repl.current_line, 'eabcd') - def test_control_events_in_large_paste(self): """Large paste events should ignore control characters""" p = events.PasteEvent() diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index c0b9bc50b..47b6fb752 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -32,13 +32,17 @@ def append_to_a(message): i.runsource('1.1.1.1') if pypy: - expected = ' File ' + green('"%s"' % _last_console_filename()) + ', line ' + \ - bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + \ - bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n' + expected = ( + ' File ' + green('"%s"' % _last_console_filename()) + + ', line ' + bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + + bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + + '\n') else: - expected = ' File ' + green('"%s"' % _last_console_filename()) + ', line ' + \ - bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + \ - bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n' + expected = ( + ' File ' + green('"%s"' % _last_console_filename()) + + ', line ' + bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + + bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + + '\n') self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) @@ -65,10 +69,11 @@ def g(): else: global_not_found = "name 'g' is not defined" - expected = 'Traceback (most recent call last):\n File ' + \ - green('"%s"' % _last_console_filename()) + ', line ' + bold(magenta('1')) + ', in ' + \ - cyan('') + '\n g()\n' + bold(red('NameError')) + ': ' + \ - cyan(global_not_found) + '\n' + expected = ( + 'Traceback (most recent call last):\n File ' + + green('"%s"' % _last_console_filename()) + ', line ' + + bold(magenta('1')) + ', in ' + cyan('') + '\n g()\n' + + bold(red('NameError')) + ': ' + cyan(global_not_found) + '\n') self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) self.assertEquals(plain('').join(a), expected) diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 3c25e3bb5..8b87be651 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -203,19 +203,19 @@ def test_transpose_character_before_cursor(self): def test_transpose_empty_line(self): self.assertEquals(transpose_character_before_cursor(0, ''), - (0,'')) + (0, '')) def test_transpose_first_character(self): self.assertEquals(transpose_character_before_cursor(0, 'a'), - (0, 'a')) + (0, 'a')) self.assertEquals(transpose_character_before_cursor(0, 'as'), - (0, 'as')) - + (0, 'as')) + def test_transpose_end_of_line(self): self.assertEquals(transpose_character_before_cursor(1, 'a'), - (1, 'a')) + (1, 'a')) self.assertEquals(transpose_character_before_cursor(2, 'as'), - (2, 'sa')) + (2, 'sa')) def test_transpose_word_before_cursor(self): pass @@ -245,7 +245,8 @@ def setUp(self): self.edits = UnconfiguredEdits() def test_seq(self): - f = lambda cursor_offset, line: ('hi', 2) + def f(cursor_offset, line): + return ('hi', 2) self.edits.add('a', f) self.assertIn('a', self.edits) self.assertEqual(self.edits['a'], f) @@ -257,24 +258,33 @@ def test_seq(self): self.edits.call('b') def test_functions_with_bad_signatures(self): - f = lambda something: (1, 2) + def f(something): + return (1, 2) with self.assertRaises(TypeError): self.edits.add('a', f) - g = lambda cursor_offset, line, something, something_else: (1, 2) + + def g(cursor_offset, line, something, something_else): + return (1, 2) with self.assertRaises(TypeError): self.edits.add('a', g) def test_functions_with_bad_return_values(self): - f = lambda cursor_offset, line: ('hi',) + def f(cursor_offset, line): + return ('hi',) with self.assertRaises(ValueError): self.edits.add('a', f) - g = lambda cursor_offset, line: ('hi', 1, 2, 3) + + def g(cursor_offset, line): + return ('hi', 1, 2, 3) with self.assertRaises(ValueError): self.edits.add('b', g) def test_config(self): - f = lambda cursor_offset, line: ('hi', 2) - g = lambda cursor_offset, line: ('hey', 3) + def f(cursor_offset, line): + return ('hi', 2) + + def g(cursor_offset, line): + return ('hey', 3) self.edits.add_config_attr('att', f) self.assertNotIn('att', self.edits) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 093dbc293..342fd3930 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -426,7 +426,8 @@ def test_paremeter_name_completion(self): self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) - self.assertEqual(self.repl.matches_iter.matches, ['abc=', 'abd=', 'abs(']) + self.assertEqual(self.repl.matches_iter.matches, + ['abc=', 'abd=', 'abs(']) class TestCliRepl(unittest.TestCase): diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 06b1dadb8..8286c9527 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -21,17 +21,17 @@ def test_matches_stdlib(self): def test_indexing(self): """Literals can be indexed into""" self.assertEqual(simple_eval('[1,2][0]'), 1) - self.assertEqual(simple_eval('a', {'a':1}), 1) + self.assertEqual(simple_eval('a', {'a': 1}), 1) def test_name_lookup(self): """Names can be lookup up in a namespace""" - self.assertEqual(simple_eval('a', {'a':1}), 1) + self.assertEqual(simple_eval('a', {'a': 1}), 1) self.assertEqual(simple_eval('map'), map) - self.assertEqual(simple_eval('a[b]', {'a':{'c':1}, 'b':'c'}), 1) + self.assertEqual(simple_eval('a[b]', {'a': {'c': 1}, 'b': 'c'}), 1) def test_allow_name_lookup(self): """Names can be lookup up in a namespace""" - self.assertEqual(simple_eval('a', {'a':1}), 1) + self.assertEqual(simple_eval('a', {'a': 1}), 1) def test_lookup_on_suspicious_types(self): class FakeDict(object): @@ -72,7 +72,7 @@ def __add__(inner_self, other): def test_operators_on_numbers(self): self.assertEqual(simple_eval('-2'), -2) self.assertEqual(simple_eval('1 + 1'), 2) - self.assertEqual(simple_eval('a - 2', {'a':1}), -1) + self.assertEqual(simple_eval('a - 2', {'a': 1}), -1) with self.assertRaises(ValueError): simple_eval('2 * 3') with self.assertRaises(ValueError): @@ -86,6 +86,7 @@ def test_nonexistant_names_raise(self): with self.assertRaises(EvaluationError): simple_eval('a') + class TestEvaluateCurrentExpression(unittest.TestCase): def assertEvaled(self, line, value, ns=None): @@ -119,7 +120,7 @@ def test_nonsense(self): self.assertEvaled('"asdf"[1].a|bc', 's') def test_with_namespace(self): - self.assertEvaled('a[1].a|bc', 'd', {'a':'adsf'}) + self.assertEvaled('a[1].a|bc', 'd', {'a': 'adsf'}) self.assertCannotEval('a[1].a|bc', {}) if __name__ == '__main__': From 4e5db7c41765fb155e1ed95ac0c817a1764f628e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 16 Dec 2015 17:29:49 -0500 Subject: [PATCH 0694/1650] fix #591 changing stack trace linenos --- bpython/repl.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index b93f3368d..1e36130f5 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -29,6 +29,7 @@ import os import pkgutil import pydoc +import re import shlex import subprocess import sys @@ -133,6 +134,9 @@ def showsyntaxerror(self, filename=None): # Stuff in the right filename and right lineno if not py3: lineno -= 1 + if re.match(r'', filename): + filename = '' + # strip linecache line number value = SyntaxError(msg, (filename, lineno, offset, line)) sys.last_value = value list = traceback.format_exception_only(type, value) @@ -149,9 +153,14 @@ def showtraceback(self): sys.last_traceback = tb tblist = traceback.extract_tb(tb) del tblist[:1] - # Set the right lineno (encoding header adds an extra line) - if not py3: - for i, (fname, lineno, module, something) in enumerate(tblist): + + for i, (fname, lineno, module, something) in enumerate(tblist): + # strip linecache line number + if re.match(r'', fname): + fname = '' + tblist[i] = (fname, lineno, module, something) + # Set the right lineno (encoding header adds an extra line) + if not py3: if fname == '': tblist[i] = (fname, lineno - 1, module, something) From 4cb4e77a9230670e81e3e028ff19c2c1313ca01e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 17 Dec 2015 08:54:56 -0500 Subject: [PATCH 0695/1650] Fix tests --- bpython/test/test_interpreter.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 47b6fb752..3a6de8d62 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals -import linecache import sys from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain @@ -14,12 +13,6 @@ pypy = 'PyPy' in sys.version -def _last_console_filename(): - """Returns the last 'filename' used for console input - (as will be displayed in a traceback).""" - return '' % (len(linecache.cache.bpython_history) - 1) - - class TestInterpreter(unittest.TestCase): def test_syntaxerror(self): i = interpreter.Interp() @@ -33,13 +26,13 @@ def append_to_a(message): if pypy: expected = ( - ' File ' + green('"%s"' % _last_console_filename()) + + ' File ' + green('""') + ', line ' + bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n') else: expected = ( - ' File ' + green('"%s"' % _last_console_filename()) + + ' File ' + green('""') + ', line ' + bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n') @@ -62,7 +55,7 @@ def f(): def g(): return f() - i.runsource('g()', encode=False) + i.runsource('g()') if pypy: global_not_found = "global name 'g' is not defined" @@ -71,7 +64,7 @@ def g(): expected = ( 'Traceback (most recent call last):\n File ' + - green('"%s"' % _last_console_filename()) + ', line ' + + green('""') + ', line ' + bold(magenta('1')) + ', in ' + cyan('') + '\n g()\n' + bold(red('NameError')) + ': ' + cyan(global_not_found) + '\n') From 9f1a339c6e5bdb88eb0a9a1484e85918e79a768b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 17 Dec 2015 10:06:33 -0500 Subject: [PATCH 0696/1650] add a blank line after Python 2 source encoding fixes #578 --- bpython/repl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index b93f3368d..7f96017b2 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -104,7 +104,7 @@ def runsource(self, source, filename=None, symbol='single', code.InteractiveInterpreter.runsource. If encode is True, the source will be encoded. On Python 3.X, encode will be ignored.""" if not py3 and encode: - source = u'# coding: %s\n%s' % (self.encoding, source) + source = u'# coding: %s\n\n%s' % (self.encoding, source) source = source.encode(self.encoding) if filename is None: filename = filename_for_console_input(source) @@ -132,7 +132,7 @@ def showsyntaxerror(self, filename=None): else: # Stuff in the right filename and right lineno if not py3: - lineno -= 1 + lineno -= 2 value = SyntaxError(msg, (filename, lineno, offset, line)) sys.last_value = value list = traceback.format_exception_only(type, value) @@ -153,7 +153,7 @@ def showtraceback(self): if not py3: for i, (fname, lineno, module, something) in enumerate(tblist): if fname == '': - tblist[i] = (fname, lineno - 1, module, something) + tblist[i] = (fname, lineno - 2, module, something) l = traceback.format_list(tblist) if l: From 2c5ada70789f4cc3abff064f1ac0c0f27b0f15b2 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 17 Dec 2015 10:17:37 -0500 Subject: [PATCH 0697/1650] changelog formatting and addition --- CHANGELOG | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a4e6be1bf..16d0dbd4f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,13 +19,14 @@ New features: Fixes: * #484: Switch `bpython.embed` to the curtsies frontend. -* #548 fix transpose character bug. Thanks to Wes E. Vial. +* #548 Fix transpose character bug. Thanks to Wes E. Vial. * #527 -q disables version banner. -* #544 fix Jedi completion error -* #536 fix completion on old-style classes with custom __getattr__ -* #480 fix old-style class autocompletion. Thanks to Joe Jevnik. +* #544 Fix Jedi completion error +* #536 Fix completion on old-style classes with custom __getattr__ +* #480 Fix old-style class autocompletion. Thanks to Joe Jevnik. * #506 in python -i mod.py sys.modules[__name__] refers to module dict -* Exceptions in autcompletion are now logged instead of crashing bpython. +* #590 Fix "None" not being displayed +* Exception line ntcompletion are now logged instead of crashing bpython. * Fix reload in Python 3. Thanks to sharow. * Fix keyword agument parameter name completion From 96ea96050b9851034be0b4283b176531b6cb52b5 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 22 Dec 2015 14:41:46 -0500 Subject: [PATCH 0698/1650] swap out pager when reevaluating --- bpython/curtsies.py | 11 +---------- bpython/curtsiesfrontend/_internal.py | 19 +++++++++++++++++++ bpython/curtsiesfrontend/repl.py | 11 +++++++++++ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 7f47d8145..17126fc57 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -1,6 +1,5 @@ from __future__ import absolute_import -import code import collections import io import logging @@ -104,15 +103,7 @@ def mainloop(self, interactive=True, paste=None): if interactive: # Add custom help command # TODO: add methods to run the code - self.coderunner.interp.locals['_repl'] = self - - self.coderunner.interp.runsource( - 'from bpython.curtsiesfrontend._internal ' - 'import _Helper') - self.coderunner.interp.runsource('help = _Helper(_repl)\n') - - del self.coderunner.interp.locals['_repl'] - del self.coderunner.interp.locals['_Helper'] + self.initialize_interp() # run startup file self.process_event(bpythonevents.RunStartupFileEvent()) diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 501ce5da6..e9f260402 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -25,6 +25,18 @@ import bpython._internal +class NopPydocPager(object): + def __enter__(self): + self._orig_pager = pydoc.pager + pydoc.pager = self + + def __exit__(self, *args): + pydoc.pager = self._orig_pager + + def __call__(self, text): + return None + + class _Helper(bpython._internal._Helper): def __init__(self, repl=None): @@ -36,4 +48,11 @@ def __init__(self, repl=None): def pager(self, output): self._repl.pager(output) + def __call__(self, *args, **kwargs): + if self._repl.reevaluating: + with NopPydocPager(): + return super(_Helper, self).__call__(*args, **kwargs) + else: + return super(_Helper, self).__call__(*args, **kwargs) + # vim: sw=4 ts=4 sts=4 ai et diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 306afb398..4f618a300 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1573,6 +1573,7 @@ def reevaluate(self, insert_into_history=False): self.interp = self.interp.__class__() self.interp.write = self.send_to_stderr self.coderunner.interp = self.interp + self.initialize_interp() self.buffer = [] self.display_buffer = [] @@ -1610,6 +1611,16 @@ def reevaluate(self, insert_into_history=False): self._cursor_offset = 0 self.current_line = '' + def initialize_interp(self): + self.coderunner.interp.locals['_repl'] = self + self.coderunner.interp.runsource( + 'from bpython.curtsiesfrontend._internal ' + 'import _Helper') + self.coderunner.interp.runsource('help = _Helper(_repl)\n') + + del self.coderunner.interp.locals['_repl'] + del self.coderunner.interp.locals['_Helper'] + def getstdout(self): lines = self.lines_for_display + [self.current_line_formatted] s = '\n'.join(x.s if isinstance(x, FmtStr) else x for x in lines) \ From 427d3710ca6cbc033cc82648db42434baa6c056e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 22 Dec 2015 16:24:12 -0600 Subject: [PATCH 0699/1650] hack for partial multiprocessing support --- bpython/curtsiesfrontend/repl.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 4f618a300..1de2640b6 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -201,6 +201,13 @@ def write(self, value): # others, so here's a hack to keep them happy raise IOError(errno.EBADF, "sys.stdin is read-only") + def close(self): + # hack to make closing stdin a nop + # This is useful for multiprocessing.Process, which does work + # for the most part, although output from other processes is + # discarded. + pass + @property def encoding(self): return 'UTF8' From 455932864ab25b7422b04af94930f434c17700e5 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 22 Dec 2015 12:41:11 -0500 Subject: [PATCH 0700/1650] Use Curtsies interpreter for -i file --- bpython/curtsies.py | 3 ++- bpython/curtsiesfrontend/interpreter.py | 11 +++++++++-- bpython/curtsiesfrontend/repl.py | 5 +++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 17126fc57..3f0f4be45 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -13,6 +13,7 @@ from bpython.curtsiesfrontend.repl import BaseRepl from bpython.curtsiesfrontend.coderunner import SystemExitFromCodeGreenlet +from bpython.curtsiesfrontend.interpreter import Interp from bpython import args as bpargs from bpython import translations from bpython.translations import _ @@ -165,7 +166,7 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): paste.events.extend(sourcecode) else: try: - interp = code.InteractiveInterpreter(locals=locals_) + interp = Interp(locals=locals_) bpargs.exec_code(interp, exec_args) except SystemExit as e: exit_value = e.args diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 5f325a84c..410a9f3fa 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,6 +1,6 @@ import sys from codeop import CommandCompiler -from six import iteritems +from six import iteritems, text_type from pygments.token import Generic, Token, Keyword, Name, Comment, String from pygments.token import Error, Literal, Number, Operator, Punctuation @@ -83,7 +83,14 @@ def __init__(self, locals=None, encoding=None): self.compile = CommandCompiler() # typically changed after being instantiated - self.write = lambda stuff: sys.stderr.write(stuff) + # but used when interpreter used corresponding REPL + def write(err_line): + """Default stderr handler for tracebacks + + Accepts FmtStrs so interpreters can output them""" + sys.stderr.write(text_type(err_line)) + + self.write = write self.outfile = self def writetb(self, lines): diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 1de2640b6..554d8035c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1119,6 +1119,7 @@ def get_current_block(self): return '\n'.join(self.buffer + [self.current_line]) def send_to_stdout(self, output): + """Send unicode string to Repl stdout""" lines = output.split('\n') logger.debug('display_lines: %r', self.display_lines) self.current_stdouterr_line += lines[0] @@ -1133,6 +1134,10 @@ def send_to_stdout(self, output): logger.debug('display_lines: %r', self.display_lines) def send_to_stderr(self, error): + """Send unicode strings or FmtStr to Repl stderr + + Must be able to handle FmtStrs because interepter pass in + tracebacks already formatted.""" lines = error.split('\n') if lines[-1]: self.current_stdouterr_line += lines[-1] From 41cc56e4d5b6a3a6d067bbfd536a2a9626fb214b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 22 Dec 2015 12:41:32 -0500 Subject: [PATCH 0701/1650] pep8 --- bpython/curtsiesfrontend/repl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 554d8035c..581311ca5 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1453,8 +1453,8 @@ def move_screen_up(current_line_start_row): rows, columns, [events.pp_event(x) for x in self.last_events if x], self.config) - arr[arr.height-last_key_box.height:arr.height, - arr.width-last_key_box.width:arr.width] = last_key_box + arr[arr.height - last_key_box.height:arr.height, + arr.width - last_key_box.width:arr.width] = last_key_box if self.config.color_scheme['background'] not in ('d', 'D'): for r in range(arr.height): @@ -1475,7 +1475,7 @@ def in_paste_mode(self): def __repr__(self): s = '' - s += '<'+repr(type(self))+'\n' + s += '<' + repr(type(self)) + '\n' s += " cursor_offset:" + repr(self.cursor_offset) + '\n' s += " num display lines:" + repr(len(self.display_lines)) + '\n' s += " lines scrolled down:" + repr(self.scroll_offset) + '\n' From 57d6e78be8177c9bedd54ea9a9f40d5716fcf5a4 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 22 Dec 2015 13:15:56 -0500 Subject: [PATCH 0702/1650] remove debugging print --- bpython/test/test_curtsies_repl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index d9f278069..03a7b8ae3 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -341,7 +341,6 @@ def test_module_content_changed(self): with self.open(fullpath) as f: f.write('a = 0\n') self.head(path) - print(sys.path) self.push('import %s' % (modname)) self.push('a = %s.a' % (modname)) self.assertIn('a', self.repl.interp.locals) From c064fd00fff8625dc95475f80ccc7e8cc967558e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 22 Dec 2015 13:32:11 -0500 Subject: [PATCH 0703/1650] dont reevaluate if previous line was empty --- bpython/curtsiesfrontend/repl.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 581311ca5..2623abea2 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1561,9 +1561,16 @@ def take_back_buffer_line(self): self.buffer.pop() self.history.pop() + def take_back_empty_line(self): + assert self.history and not self.history[-1] + self.history.pop() + self.display_lines.pop() + def prompt_undo(self): if self.buffer: return self.take_back_buffer_line() + if self.history and not self.history[-1]: + return self.take_back_empty_line() def prompt_for_undo(): n = BpythonRepl.prompt_undo(self) From 62e49f1ee5a0c944ece99e1403a757039ee4db65 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 23 Dec 2015 12:35:59 -0800 Subject: [PATCH 0704/1650] changelog updates --- CHANGELOG | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 16d0dbd4f..cfd072c98 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,9 +24,11 @@ Fixes: * #544 Fix Jedi completion error * #536 Fix completion on old-style classes with custom __getattr__ * #480 Fix old-style class autocompletion. Thanks to Joe Jevnik. -* #506 in python -i mod.py sys.modules[__name__] refers to module dict +* #506 In python -i mod.py sys.modules[__name__] refers to module dict * #590 Fix "None" not being displayed -* Exception line ntcompletion are now logged instead of crashing bpython. +* #546 Paste detection uses events instead of bytes returned in a single + os.read call +* Exceptions in autcompletion are now logged instead of crashing bpython. * Fix reload in Python 3. Thanks to sharow. * Fix keyword agument parameter name completion From e6d6efb62079809cec3f742d74ec53dc31a8302b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 24 Dec 2015 07:40:18 -0800 Subject: [PATCH 0705/1650] README typos --- README.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index aae559515..cef77c8e9 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ usage). bpython does **not** aim to be a complete IDE - the focus is on implementing a few ideas in a practical, useful, and lightweight manner. -bpython is a great replacement to any occassion where you would normally use the +bpython is a great replacement to any occasion where you would normally use the vanilla Python interpreter - testing out solutions to people's problems on IRC, quickly testing a method of doing something without creating a temporary file, etc.. @@ -62,7 +62,7 @@ Features & Examples * Rewind. This isn't called "Undo" because it would be misleading, but "Rewind" is probably as bad. The idea is that the code entered is kept in memory and when the Rewind function is called, the last line is popped and the entire - code is re-evaluated. + code is re-evaluated. * Pastebin code/write to file. Use the key to upload the screen's contents to pastebin, with a URL returned. @@ -75,7 +75,7 @@ Features & Examples Configuration ============= See the sample-config file for a list of available options. You should save -your config file as **~/.config/bpython/config** (i.e +your config file as **~/.config/bpython/config** (i.e. ``$XDG_CONFIG_HOME/bpython/config``) or specify at the command line:: bpython --config /path/to/bpython/config @@ -95,7 +95,7 @@ Dependencies Python 2 before 2.7.7 --------------------- -If you are using Python 2 before 2.7.7, the followign dependency is also +If you are using Python 2 before 2.7.7, the following dependency is also required: * requests[security] @@ -120,7 +120,7 @@ For known bugs please see bpython's `known issues and FAQ`_ page. Contact & Contributing ====================== I hope you find it useful and please feel free to submit any bugs/patches -suggestions to `Robert`_ or place them on the github +suggestions to `Robert`_ or place them on the GitHub `issues tracker`_. For any other ways of communicating with bpython users and devs you can find us From cb7e36e2aa1b3c9c5fb75adda87ae9282a670daf Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 24 Dec 2015 08:05:31 -0800 Subject: [PATCH 0706/1650] add readme features --- README.rst | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index cef77c8e9..e6f9a2085 100644 --- a/README.rst +++ b/README.rst @@ -44,15 +44,16 @@ If you have `pip`_ installed, you can simply run: $ pip install bpython Start bpython by typing ``bpython`` in your terminal. You can exit bpython by -using the ``exit()`` command. +using the ``exit()`` command or by pressing control-D like regular interactive +Python. =================== Features & Examples =================== -* In-line syntax highlighting. This uses Pygments for lexing the code as you - type, and colours appropriately. +* Readline-like autocomplete, with suggestions displayed as you type. -* Readline-like autocomplete. Suggestions displayed as you type. +* In-line syntax highlighting. This uses Pygments for lexing the code as you + type, and colours appropriately. * Expected parameter list. As in a lot of modern IDEs, bpython will attempt to display a list of parameters for any function you call. The inspect module is @@ -62,14 +63,17 @@ Features & Examples * Rewind. This isn't called "Undo" because it would be misleading, but "Rewind" is probably as bad. The idea is that the code entered is kept in memory and when the Rewind function is called, the last line is popped and the entire - code is re-evaluated. + session is re-evaluated. Use to rewind. + +* Edit the current line or your entire session in an editor. F7 opens the current + session in a text editor, and if modifications are made, the session is rerun + with these changes. * Pastebin code/write to file. Use the key to upload the screen's contents to pastebin, with a URL returned. -* Flush curses screen to stdout. When you quit bpython, the screen data will be - flushed to stdout, so it basically looks the same as if you had quit the - vanilla interpreter. +* Reload imported Python modules. Use to clear sys.modules and rerun your + session to test changes to code in a module you're working on. ============= Configuration From 97078108bc54be81825ab68d9f0db0c1ec3649cd Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 24 Dec 2015 18:28:43 -0800 Subject: [PATCH 0707/1650] correct layout for short terminals --- bpython/curtsiesfrontend/repl.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2623abea2..54ec150a0 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1375,6 +1375,7 @@ def move_screen_up(current_line_start_row): lines = paint.display_linize(self.current_cursor_line + 'X', width) # extra character for space for the cursor current_line_end_row = current_line_start_row + len(lines) - 1 + current_line_height = current_line_end_row - current_line_start_row if self.stdin.has_focus: cursor_row, cursor_column = divmod( @@ -1410,9 +1411,10 @@ def move_screen_up(current_line_start_row): info_max_rows = max(visible_space_above, visible_space_below) else: # smallest allowed over-full completion box - minimum_possible_height = 30 - info_max_rows = max(visible_space_below, - minimum_possible_height) + minimum_possible_height = 20 + info_max_rows = min(max(visible_space_below, + minimum_possible_height), + min_height - current_line_height - 1) infobox = paint.paint_infobox( info_max_rows, int(width * self.config.cli_suggestion_width), From 835847ded3379dea145defc57413234a0d602f8d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 25 Dec 2015 12:05:24 -0800 Subject: [PATCH 0708/1650] ignore warnings in python2.6 --- bpython/test/test_args.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index ef53cfda9..b85eda7d0 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -30,7 +30,9 @@ def test_exec_dunder_file(self): sys.stderr.flush()""")) f.flush() p = subprocess.Popen( - [sys.executable, "-m", "bpython.curtsies", f.name], + [sys.executable] + + (['-W', 'ignore'] if sys.version_info[:2] == (2, 6) else []) + + ["-m", "bpython.curtsies", f.name], stderr=subprocess.PIPE, universal_newlines=True) (_, stderr) = p.communicate() From 6b7f681b065ace37b6b97f19ae2447067f60146c Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 24 Dec 2015 11:50:40 -0800 Subject: [PATCH 0709/1650] factor out and test is_new_style --- bpython/inspection.py | 11 +++++- bpython/test/test_inspection.py | 60 +++++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index a66a04e79..88e2dfdd5 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -69,7 +69,7 @@ def __enter__(self): # original methods. :-( # The upshot being that introspecting on an object to display its # attributes will avoid unwanted side-effects. - if py3 or type_ != types.InstanceType: + if is_new_style(self.obj): __getattr__ = getattr(type_, '__getattr__', None) if __getattr__ is not None: try: @@ -98,6 +98,15 @@ def __exit__(self, exc_type, exc_val, exc_tb): # /Dark magic +if py3: + def is_new_style(obj): + return True +else: + def is_new_style(obj): + """Returns True if obj is a new-style class or object""" + return type(obj) != types.InstanceType + + class _Repr(object): """ Helper for `fixlongargs()`: Returns the given value in `__repr__()`. diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index cf5a1aad5..d860bea55 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -2,6 +2,7 @@ import os +from bpython._py3compat import py3 from bpython import inspection from bpython.test import unittest from bpython.test.fodder import encoding_ascii @@ -20,29 +21,35 @@ ''' -class TestInspection(unittest.TestCase): - def test_is_callable(self): - class OldCallable: - def __call__(self): - pass +class OldCallable: + def __call__(self): + pass - class Callable(object): - def __call__(self): - pass - class OldNoncallable: - pass +class Callable(object): + def __call__(self): + pass - class Noncallable(object): - pass - def spam(): - pass +class OldNoncallable: + pass + + +class Noncallable(object): + pass - class CallableMethod(object): - def method(self): - pass +def spam(): + pass + + +class CallableMethod(object): + def method(self): + pass + + +class TestInspection(unittest.TestCase): + def test_is_callable(self): self.assertTrue(inspection.is_callable(spam)) self.assertTrue(inspection.is_callable(Callable)) self.assertTrue(inspection.is_callable(Callable())) @@ -53,6 +60,25 @@ def method(self): self.assertFalse(inspection.is_callable(None)) self.assertTrue(inspection.is_callable(CallableMethod().method)) + @unittest.skipIf(py3, 'old-style classes only exist in Python 2') + def test_is_new_style_py2(self): + self.assertTrue(inspection.is_new_style(spam)) + self.assertTrue(inspection.is_new_style(Noncallable)) + self.assertFalse(inspection.is_new_style(OldNoncallable)) + self.assertTrue(inspection.is_new_style(Noncallable())) + self.assertFalse(inspection.is_new_style(OldNoncallable())) + self.assertTrue(inspection.is_new_style(None)) + + @unittest.skipUnless(py3, 'only in Python 3 are all classes new-style') + def test_is_new_style_py3(self): + self.assertTrue(inspection.is_new_style(spam)) + self.assertTrue(inspection.is_new_style(Noncallable)) + self.assertTrue(inspection.is_new_style(OldNoncallable)) + self.assertTrue(inspection.is_new_style(Noncallable())) + self.assertTrue(inspection.is_new_style(OldNoncallable())) + self.assertTrue(inspection.is_new_style(None)) + + def test_parsekeywordpairs(self): # See issue #109 def fails(spam=['-a', '-b']): From 8be8f739f667039b01ee45521e8becfb02f111c0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 24 Dec 2015 11:53:46 -0800 Subject: [PATCH 0710/1650] curent_word falls when if obj.attr obj not global --- bpython/line.py | 13 ++++++++----- bpython/test/test_line_properties.py | 22 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 60388b88e..fecdfcec5 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -10,9 +10,12 @@ from bpython.lazyre import LazyReCompile -current_word_re = LazyReCompile(r'[\w_][\w0-9._]*[(]?') LinePart = namedtuple('LinePart', ['start', 'stop', 'word']) +current_word_re = LazyReCompile( + r'(?= pos: - start = m.start() - end = m.end() - word = m.group() + if m.start(1) < pos and m.end(1) >= pos: + start = m.start(1) + end = m.end(1) + word = m.group(1) if word is None: return None return LinePart(start, end, word) diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 0d9f611b7..aa975f1fa 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -5,7 +5,7 @@ current_string, current_object, current_object_attribute, \ current_from_import_from, current_from_import_import, current_import, \ current_method_definition_name, current_single_word, \ - current_expression_attribute + current_expression_attribute, current_dotted_attribute def cursor(s): @@ -129,6 +129,15 @@ def test_dots(self): self.assertAccess('stuff[]') self.assertAccess('stuff[asdf[]') + def test_non_dots(self): + self.assertAccess('].asdf|') + self.assertAccess(').asdf|') + self.assertAccess('foo[0].asdf|') + self.assertAccess('foo().asdf|') + self.assertAccess('foo().|') + self.assertAccess('foo().asdf.|') + self.assertAccess('foo[0].asdf.|') + def test_open_paren(self): self.assertAccess('') # documenting current behavior - TODO is this intended? @@ -336,5 +345,16 @@ def test_strings(self): self.assertAccess('"hey".<|>') +class TestCurrentDottedAttribute(LineTestCase): + def setUp(self): + self.func = current_dotted_attribute + + def test_simple(self): + self.assertAccess('|') + self.assertAccess('(|') + self.assertAccess('[|') + self.assertAccess('m.body[0].value|') + self.assertAccess('m.body[0].attr.value|') + if __name__ == '__main__': unittest.main() From f55d7d5d81c4a9513ad7344c50da6d54c391b94c Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 24 Dec 2015 11:55:55 -0800 Subject: [PATCH 0711/1650] attribute lookup in simple_eval --- bpython/simpleeval.py | 54 +++++++++++++++- bpython/test/test_autocomplete.py | 22 +++++++ bpython/test/test_line_properties.py | 9 +-- bpython/test/test_simpleeval.py | 97 +++++++++++++++++++++++++++- 4 files changed, 176 insertions(+), 6 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index b8931a07f..a41974d3c 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -34,6 +34,7 @@ from bpython import line as line_properties from bpython._py3compat import py3 +from bpython.inspection import is_new_style _string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) _numeric_types = (int, float, complex) + (() if py3 else (long,)) @@ -72,7 +73,9 @@ def safe_eval(expr, namespace): def simple_eval(node_or_string, namespace=None): """ Safely evaluate an expression node or a string containing a Python - expression. The string or node provided may only consist of: + expression without triggering any user code. + + The string or node provided may only consist of: * the following Python literal structures: strings, numbers, tuples, lists, and dicts * variable names causing lookups in the passed in namespace or builtins @@ -144,6 +147,12 @@ def _convert(node): index = _convert(node.slice.value) return safe_getitem(obj, index) + # this is a deviation from literal_eval: we allow attribute access + if isinstance(node, ast.Attribute): + obj = _convert(node.value) + attr = node.attr + return safe_get_attribute(obj, attr) + raise ValueError('malformed string') return _convert(node_or_string) @@ -225,3 +234,46 @@ def evaluate_current_attribute(cursor_offset, line, namespace=None): except AttributeError: raise EvaluationError( "can't lookup attribute %s on %r" % (attr.word, obj)) + + +def safe_get_attribute(obj, attr): + """Gets attributes without triggering descriptors on new-style clases""" + if is_new_style(obj): + result = safe_get_attribute_new_style(obj, attr) + if isinstance(result, member_descriptor): + # will either be the same slot descriptor or the value + return getattr(obj, attr) + return result + return getattr(obj, attr) + + +class _ClassWithSlots(object): + __slots__ = ['a'] +member_descriptor = type(_ClassWithSlots.a) + + +def safe_get_attribute_new_style(obj, attr): + """Returns approximately the attribute returned by getattr(obj, attr) + + The object returned ought to be callable if getattr(obj, attr) was. + Fake callable objects may be returned instead, in order to avoid executing + arbitrary code in descriptors. + + If the object is an instance of a class that uses __slots__, will return + the member_descriptor object instead of the value. + """ + if not is_new_style(obj): + raise ValueError("%r is not a new-style class or object" % obj) + to_look_through = (obj.mro() + if hasattr(obj, 'mro') + else [obj] + type(obj).mro()) + + found_in_slots = hasattr(obj, '__slots__') and attr in obj.__slots__ + for cls in to_look_through: + if hasattr(cls, '__dict__') and attr in cls.__dict__: + return cls.__dict__[attr] + + if found_in_slots: + return AttributeIsEmptySlot + + raise AttributeError() diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 87fda85a4..16c633558 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -235,6 +235,17 @@ def method(self, x): 'In Python 3 there are no old style classes') +class Properties(Foo): + + @property + def asserts_when_called(self): + raise AssertionError("getter method called") + + +class Slots(object): + __slots__ = ['a', 'b'] + + class TestAttrCompletion(unittest.TestCase): @classmethod def setUpClass(cls): @@ -268,6 +279,17 @@ def __getattr__(self, attr): self.assertIn(u'a.__module__', self.com.matches(4, 'a.__', locals_=locals_)) + def test_descriptor_attributes_not_run(self): + com = autocomplete.AttrCompletion() + self.assertSetEqual(com.matches(2, 'a.', locals_={'a': Properties()}), + set(['a.b', 'a.a', 'a.method', + 'a.asserts_when_called'])) + + def test_slots_not_crash(self): + com = autocomplete.AttrCompletion() + self.assertSetEqual(com.matches(2, 'A.', locals_={'A': Slots}), + set(['A.b', 'A.a', 'A.mro'])) + class TestExpressionAttributeCompletion(unittest.TestCase): @classmethod diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index aa975f1fa..4638084c2 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -11,7 +11,7 @@ def cursor(s): """'ab|c' -> (2, 'abc')""" cursor_offset = s.index('|') - line = s[:cursor_offset] + s[cursor_offset+1:] + line = s[:cursor_offset] + s[cursor_offset + 1:] return cursor_offset, line @@ -53,11 +53,12 @@ def encode(cursor_offset, line, result): if start < cursor_offset: encoded_line = encoded_line[:start] + '<' + encoded_line[start:] else: - encoded_line = encoded_line[:start+1] + '<' + encoded_line[start+1:] + encoded_line = (encoded_line[:start + 1] + '<' + + encoded_line[start + 1:]) if end < cursor_offset: - encoded_line = encoded_line[:end+1] + '>' + encoded_line[end+1:] + encoded_line = encoded_line[:end + 1] + '>' + encoded_line[end + 1:] else: - encoded_line = encoded_line[:end+2] + '>' + encoded_line[end+2:] + encoded_line = encoded_line[:end + 2] + '>' + encoded_line[end + 2:] return encoded_line diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 8286c9527..3488c5027 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -5,8 +5,11 @@ from bpython.simpleeval import (simple_eval, evaluate_current_expression, - EvaluationError) + EvaluationError, + safe_get_attribute, + safe_get_attribute_new_style) from bpython.test import unittest +from bpython._py3compat import py3 class TestSimpleEval(unittest.TestCase): @@ -86,6 +89,12 @@ def test_nonexistant_names_raise(self): with self.assertRaises(EvaluationError): simple_eval('a') + def test_attribute_access(self): + class Foo(object): + abc = 1 + + self.assertEqual(simple_eval('foo.abc', {'foo': Foo()}), 1) + class TestEvaluateCurrentExpression(unittest.TestCase): @@ -123,5 +132,91 @@ def test_with_namespace(self): self.assertEvaled('a[1].a|bc', 'd', {'a': 'adsf'}) self.assertCannotEval('a[1].a|bc', {}) + +class A(object): + a = 'a' + + +class B(A): + b = 'b' + + +class Property(object): + @property + def prop(self): + raise AssertionError('Property __get__ executed') + + +class Slots(object): + __slots__ = ['s1', 's2', 's3'] + + if not py3: + @property + def s3(self): + raise AssertionError('Property __get__ executed') + + +class SlotsSubclass(Slots): + @property + def s4(self): + raise AssertionError('Property __get__ executed') + + +member_descriptor = type(Slots.s1) + + +class TestSafeGetAttribute(unittest.TestCase): + + def test_lookup_on_object(self): + a = A() + a.x = 1 + self.assertEquals(safe_get_attribute_new_style(a, 'x'), 1) + self.assertEquals(safe_get_attribute_new_style(a, 'a'), 'a') + b = B() + b.y = 2 + self.assertEquals(safe_get_attribute_new_style(b, 'y'), 2) + self.assertEquals(safe_get_attribute_new_style(b, 'a'), 'a') + self.assertEquals(safe_get_attribute_new_style(b, 'b'), 'b') + + def test_avoid_running_properties(self): + p = Property() + self.assertEquals(safe_get_attribute_new_style(p, 'prop'), + Property.prop) + + @unittest.skipIf(py3, 'Old-style classes not in Python 3') + def test_raises_on_old_style_class(self): + class Old: + pass + with self.assertRaises(ValueError): + safe_get_attribute_new_style(Old, 'asdf') + + def test_lookup_with_slots(self): + s = Slots() + s.s1 = 's1' + self.assertEquals(safe_get_attribute(s, 's1'), 's1') + self.assertIsInstance(safe_get_attribute_new_style(s, 's1'), + member_descriptor) + with self.assertRaises(AttributeError): + safe_get_attribute(s, 's2') + self.assertIsInstance(safe_get_attribute_new_style(s, 's2'), + member_descriptor) + + def test_lookup_on_slots_classes(self): + sga = safe_get_attribute + s = SlotsSubclass() + self.assertIsInstance(sga(Slots, 's1'), member_descriptor) + self.assertIsInstance(sga(SlotsSubclass, 's1'), member_descriptor) + self.assertIsInstance(sga(SlotsSubclass, 's4'), property) + self.assertIsInstance(sga(s, 's4'), property) + + @unittest.skipIf(py3, "Py 3 doesn't allow slots and prop in same class") + def test_lookup_with_property_and_slots(self): + sga = safe_get_attribute + s = SlotsSubclass() + self.assertIsInstance(sga(Slots, 's3'), property) + self.assertEquals(safe_get_attribute(s, 's3'), + Slots.__dict__['s3']) + self.assertIsInstance(sga(SlotsSubclass, 's3'), property) + if __name__ == '__main__': unittest.main() From fb3dc2280595f06ba2dc45cff2b66783634f617c Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 24 Dec 2015 13:05:44 -0800 Subject: [PATCH 0712/1650] deal with overridden getattr etc. --- bpython/simpleeval.py | 24 +++++++++++------------- bpython/test/test_simpleeval.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index a41974d3c..b4ac7b89c 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -30,11 +30,12 @@ import ast from six import string_types +import inspect from six.moves import builtins from bpython import line as line_properties from bpython._py3compat import py3 -from bpython.inspection import is_new_style +from bpython.inspection import is_new_style, AttrCleaner _string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) _numeric_types = (int, float, complex) + (() if py3 else (long,)) @@ -239,11 +240,12 @@ def evaluate_current_attribute(cursor_offset, line, namespace=None): def safe_get_attribute(obj, attr): """Gets attributes without triggering descriptors on new-style clases""" if is_new_style(obj): - result = safe_get_attribute_new_style(obj, attr) - if isinstance(result, member_descriptor): - # will either be the same slot descriptor or the value - return getattr(obj, attr) - return result + with AttrCleaner(obj): + result = safe_get_attribute_new_style(obj, attr) + if isinstance(result, member_descriptor): + # will either be the same slot descriptor or the value + return getattr(obj, attr) + return result return getattr(obj, attr) @@ -264,16 +266,12 @@ def safe_get_attribute_new_style(obj, attr): """ if not is_new_style(obj): raise ValueError("%r is not a new-style class or object" % obj) - to_look_through = (obj.mro() - if hasattr(obj, 'mro') - else [obj] + type(obj).mro()) + to_look_through = (obj.__mro__ + if inspect.isclass(obj) + else (obj,) + type(obj).__mro__) - found_in_slots = hasattr(obj, '__slots__') and attr in obj.__slots__ for cls in to_look_through: if hasattr(cls, '__dict__') and attr in cls.__dict__: return cls.__dict__[attr] - if found_in_slots: - return AttributeIsEmptySlot - raise AttributeError() diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 3488c5027..d997fd912 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -162,6 +162,24 @@ def s4(self): raise AssertionError('Property __get__ executed') +class OverridenGetattr(object): + def __getattr__(self, attr): + raise AssertionError('custom __getattr__ executed') + a = 1 + + +class OverridenGetattribute(object): + def __getattribute__(self, attr): + raise AssertionError('custom __getattrribute__ executed') + a = 1 + + +class OverridenMRO(object): + def __mro__(self): + raise AssertionError('custom mro executed') + a = 1 + + member_descriptor = type(Slots.s1) @@ -218,5 +236,17 @@ def test_lookup_with_property_and_slots(self): Slots.__dict__['s3']) self.assertIsInstance(sga(SlotsSubclass, 's3'), property) + def test_lookup_on_overriden_methods(self): + sga = safe_get_attribute + self.assertEqual(sga(OverridenGetattr(), 'a'), 1) + self.assertEqual(sga(OverridenGetattribute(), 'a'), 1) + self.assertEqual(sga(OverridenMRO(), 'a'), 1) + with self.assertRaises(AttributeError): + sga(OverridenGetattr(), 'b') + with self.assertRaises(AttributeError): + sga(OverridenGetattribute(), 'b') + with self.assertRaises(AttributeError): + sga(OverridenMRO(), 'b') + if __name__ == '__main__': unittest.main() From 134fb1e5a85a973d6df2206ce1dc040ec266466d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 24 Dec 2015 18:08:26 -0800 Subject: [PATCH 0713/1650] factor out current function --- bpython/repl.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index dd6855649..3e7dd0400 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -477,22 +477,14 @@ def get_object(self, name): obj = getattr(obj, attributes.pop(0)) return obj - def get_args(self): - """Check if an unclosed parenthesis exists, then attempt to get the - argspec() for it. On success, update self.funcprops,self.arg_pos and - return True, otherwise set self.funcprops to None and return False""" - - self.current_func = None + @classmethod + def _funcname_and_argnum(cls, line): + """Name of the current function and where we are in args - if not self.config.arg_spec: - return False - - # Get the name of the current function and where we are in - # the arguments + Returns (None, None) if can't be found.""" stack = [['', 0, '']] try: - for (token, value) in PythonLexer().get_tokens( - self.current_line): + for (token, value) in PythonLexer().get_tokens(line): if token is Token.Punctuation: if value in '([{': stack.append(['', 0, value]) @@ -522,8 +514,21 @@ def get_args(self): stack.pop() _, arg_number, _ = stack.pop() func, _, _ = stack.pop() + return func, arg_number except IndexError: + return None, None + + def get_args(self): + """Check if an unclosed parenthesis exists, then attempt to get the + argspec() for it. On success, update self.funcprops,self.arg_pos and + return True, otherwise set self.funcprops to None and return False""" + + self.current_func = None + + if not self.config.arg_spec: return False + + func, arg_number = self._funcname_and_argnum(self.current_line) if not func: return False From 3a808fa38ef51d5e63ffdeb4d968e5a8069e1854 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 24 Dec 2015 18:10:02 -0800 Subject: [PATCH 0714/1650] function parser finds function expressions --- bpython/repl.py | 56 ++++++++++++++++++++++++++------------- bpython/test/test_repl.py | 24 +++++++++++++++++ 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 3e7dd0400..2beb868f7 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -479,41 +479,59 @@ def get_object(self, name): @classmethod def _funcname_and_argnum(cls, line): - """Name of the current function and where we are in args + # each list in stack: + # [full_expr, function_expr, arg_number, opening] + # empty string in num_commas means we've encountered a kwarg + # so we're done counting - Returns (None, None) if can't be found.""" - stack = [['', 0, '']] + # new plan: do a full parse, then combine things + stack = [['', '', 0, '']] try: for (token, value) in PythonLexer().get_tokens(line): if token is Token.Punctuation: if value in '([{': - stack.append(['', 0, value]) + stack.append(['', '', 0, value]) elif value in ')]}': - stack.pop() + full, _, _, start = stack.pop() + expr = start + full + value + stack[-1][1] += expr + stack[-1][0] += expr elif value == ',': try: - stack[-1][1] += 1 + stack[-1][2] += 1 except TypeError: - stack[-1][1] = '' - stack[-1][0] = '' - elif value == ':' and stack[-1][2] == 'lambda': - stack.pop() + stack[-1][2] = '' + stack[-1][1] = '' + stack[-1][0] += value + elif value == ':' and stack[-1][3] == 'lambda': + expr = stack.pop()[0] + ':' + stack[-1][1] += expr + stack[-1][0] += expr else: - stack[-1][0] = '' - elif (token is Token.Name or token in Token.Name.subtypes or + stack[-1][1] = '' + stack[-1][0] += value + elif (token is Token.Number or + token in Token.Number.subtypes or + token is Token.Name or token in Token.Name.subtypes or token is Token.Operator and value == '.'): + stack[-1][1] += value stack[-1][0] += value elif token is Token.Operator and value == '=': - stack[-1][1] = stack[-1][0] - stack[-1][0] = '' + stack[-1][2] = stack[-1][1] + stack[-1][1] = '' + stack[-1][0] += value + elif token is Token.Number or token in Token.Number.subtypes: + stack[-1][1] = value + stack[-1][0] += value elif token is Token.Keyword and value == 'lambda': - stack.append(['', 0, value]) + stack.append([value, '', 0, value]) else: - stack[-1][0] = '' - while stack[-1][2] in '[{': + stack[-1][1] = '' + stack[-1][0] += value + while stack[-1][3] in '[{': stack.pop() - _, arg_number, _ = stack.pop() - func, _, _ = stack.pop() + _, _, arg_number, _ = stack.pop() + _, func, _, _ = stack.pop() return func, arg_number except IndexError: return None, None diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 342fd3930..a27de674d 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -238,6 +238,30 @@ def test_methods_of_expressions(self): self.assertTrue(self.repl.get_args()) +class TestArgspecInternal(unittest.TestCase): + def test_function_expressions(self): + te = self.assertTupleEqual + fa = lambda line: repl.Repl._funcname_and_argnum(line) + for line, (func, argnum) in [ + ('spam(', ('spam', 0)), + ('spam((), ', ('spam', 1)), + ('spam.eggs((), ', ('spam.eggs', 1)), + ('spam[abc].eggs((), ', ('spam[abc].eggs', 1)), + ('spam[0].eggs((), ', ('spam[0].eggs', 1)), + ('spam[a + b]eggs((), ', ('spam[a + b]eggs', 1)), + ('spam().eggs((), ', ('spam().eggs', 1)), + ('spam(1, 2).eggs((), ', ('spam(1, 2).eggs', 1)), + ('spam(1, f(1)).eggs((), ', ('spam(1, f(1)).eggs', 1)), + ('[0].eggs((), ', ('[0].eggs', 1)), + ('[0][0]((), {}).eggs((), ', ('[0][0]((), {}).eggs', 1)), + ('a + spam[0].eggs((), ', ('spam[0].eggs', 1)), + ("spam(", ("spam", 0)), + ("spam(map([]", ("map", 0)), + ("spam((), ", ("spam", 1)) + ]: + te(fa(line), (func, argnum)) + + class TestGetSource(unittest.TestCase): def setUp(self): self.repl = FakeRepl() From 2a65a478e0104b57022601e8dad861f6c717a926 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 24 Dec 2015 18:21:41 -0800 Subject: [PATCH 0715/1650] make is_new_style work on oldstyle class objects --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 88e2dfdd5..c14bfdc9b 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -104,7 +104,7 @@ def is_new_style(obj): else: def is_new_style(obj): """Returns True if obj is a new-style class or object""" - return type(obj) != types.InstanceType + return type(obj) not in [types.InstanceType, types.ClassType] class _Repr(object): From 92dccc9dee1142a0452f01d0345c73244e2a3931 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 25 Dec 2015 12:35:57 -0800 Subject: [PATCH 0716/1650] Python 2.6 compatibilty for isclass --- bpython/simpleeval.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index b4ac7b89c..8e5141c4d 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -29,9 +29,11 @@ """ import ast -from six import string_types import inspect +from six import string_types from six.moves import builtins +import sys +import types from bpython import line as line_properties from bpython._py3compat import py3 @@ -47,6 +49,14 @@ _name_type_nodes = (ast.Name,) +# inspect.isclass is broken in Python 2.6 +if sys.version_info[:2] == (2, 6): + def isclass(obj): + return isinstance(obj, (type, types.ClassType)) +else: + isclass = inspect.isclass + + class EvaluationError(Exception): """Raised if an exception occurred in safe_eval.""" @@ -267,7 +277,7 @@ def safe_get_attribute_new_style(obj, attr): if not is_new_style(obj): raise ValueError("%r is not a new-style class or object" % obj) to_look_through = (obj.__mro__ - if inspect.isclass(obj) + if isclass(obj) else (obj,) + type(obj).__mro__) for cls in to_look_through: From f6a6f940bca0a5f99dae5befc972f5d66a805828 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 25 Dec 2015 12:44:26 -0800 Subject: [PATCH 0717/1650] document _funcname_and_argnum --- bpython/repl.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 2beb868f7..eee1c4759 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -479,12 +479,11 @@ def get_object(self, name): @classmethod def _funcname_and_argnum(cls, line): + """Parse out the current function name and arg from a line of code.""" # each list in stack: # [full_expr, function_expr, arg_number, opening] - # empty string in num_commas means we've encountered a kwarg - # so we're done counting - - # new plan: do a full parse, then combine things + # arg_number may be a string if we've encountered a keyword + # argument so we're done counting stack = [['', '', 0, '']] try: for (token, value) in PythonLexer().get_tokens(line): From ad0bc4188bedabcf6adc3cd2f9d8278690ab04aa Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 25 Dec 2015 13:04:03 -0800 Subject: [PATCH 0718/1650] update changelog --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index cfd072c98..9496611ab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,7 +12,8 @@ New features: * #538: Multi-line banners are allowed. * #229: inspect.getsource works on interactively defined functions. Thanks to Michael Mulley. -* Attribute completion on items in lists and tuples +* Attribute completion works on literals and some expressions containing + builtin objects. * Ctrl-e can be used to autocomplete current fish-style suggestion. Thanks to Amjith Ramanujam From 5800e38fc397cd6ed305f7a353c60ed903fc2285 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 27 Dec 2015 09:07:42 -0800 Subject: [PATCH 0719/1650] require unicode for pager --- bpython/curtsiesfrontend/_internal.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index e9f260402..187427824 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -23,6 +23,8 @@ import pydoc import bpython._internal +from bpython._py3compat import py3 +from bpython.repl import getpreferredencoding class NopPydocPager(object): @@ -46,6 +48,8 @@ def __init__(self, repl=None): super(_Helper, self).__init__() def pager(self, output): + if not py3 and isinstance(output, str): + output = output.decode(getpreferredencoding()) self._repl.pager(output) def __call__(self, *args, **kwargs): From 7d7e600d2f85b027f84895062cc1b3ca814ab5f9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 8 Jan 2016 22:02:22 +0100 Subject: [PATCH 0720/1650] Document new options Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/configuration-options.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 07467ce78..c51eb7efd 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -308,6 +308,14 @@ Brings up sincerely cheerful description of bpython features and current key bin .. versionadded:: 0.14 +incremental_search +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Default: M-s + +Perform incremental search on all stored lines in the history. + +.. versionadded:: 0.15 + last_output ^^^^^^^^^^^ Default: F9 @@ -334,6 +342,14 @@ Reruns entire session, reloading all modules by clearing the sys.modules cache. .. versionadded:: 0.14 +reverse_incremental_search +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Default: M-r + +Perform reverse incremental search on all stored lines in the history. + +.. versionadded:: 0.15 + right ^^^^^ Default: C-f From cf6669e3983f5e8b662e94fb10b8b478e9c30444 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2016 23:29:29 +0100 Subject: [PATCH 0721/1650] List six Signed-off-by: Sebastian Ramacher --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index e6f9a2085..d5515e1d4 100644 --- a/README.rst +++ b/README.rst @@ -91,6 +91,7 @@ Dependencies * requests * curtsies >= 0.1.18 * greenlet +* six >= 1.5 * Sphinx != 1.1.2 (optional, for the documentation) * mock (optional, for the testsuite) * babel (optional, for internationalization) From 1ef2cc9190682fce6791d8ab8b8ac414a9d94ae3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2016 23:29:55 +0100 Subject: [PATCH 0722/1650] Fix formatting Signed-off-by: Sebastian Ramacher --- README.rst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index d5515e1d4..066a3631d 100644 --- a/README.rst +++ b/README.rst @@ -143,21 +143,18 @@ Dependencies `pyreadline`_ Use the version in the cheeseshop. -=========== Recommended -=========== +----------- Obtain the less program from GnuUtils. This makes the pager work as intended. It can be obtained from cygwin or GnuWin32 or msys -============================== Current version is tested with -============================== +------------------------------ * Curses 2.2 * pyreadline 1.7 -============ Curses Notes -============ +------------ The curses used has a bug where the colours are displayed incorrectly: * red is swapped with blue From 37bae58794c5ccc60111e206622a98627e983a6f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2016 23:33:01 +0100 Subject: [PATCH 0723/1650] Remove last mention of GTK+ --- doc/sphinx/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py index f0e6bce95..99f63344e 100644 --- a/doc/sphinx/source/conf.py +++ b/doc/sphinx/source/conf.py @@ -205,7 +205,7 @@ # (source start file, name, description, authors, manual section). man_pages = [ ('man-bpython', 'bpython', - u'a fancy {curses, GTK+, urwid} interface to the Python interactive interpreter', + u'a fancy {curtsies, curses, urwid} interface to the Python interactive interpreter', [], 1), ('man-bpython-config', 'bpython-config', u'user configuration file for bpython', From 1bc03d80e10a2b76f3fc3629c02f704a78d1bc28 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2016 23:33:16 +0100 Subject: [PATCH 0724/1650] Update Python versions Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/releases.rst | 2 +- doc/sphinx/source/tips.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst index 299ad1d9d..0a663843a 100644 --- a/doc/sphinx/source/releases.rst +++ b/doc/sphinx/source/releases.rst @@ -45,7 +45,7 @@ A checklist to perform some manual tests before a release: Check that all of the following work before a release: -* Runs under Python 2.6, 2.7, 3.3, and 3.4. +* Runs under Python 2.6, 2.7, 3.3, 3.4 and 3.5. * Save * Rewind * Pastebin diff --git a/doc/sphinx/source/tips.rst b/doc/sphinx/source/tips.rst index 062ce4bf7..60ea0a152 100644 --- a/doc/sphinx/source/tips.rst +++ b/doc/sphinx/source/tips.rst @@ -16,7 +16,7 @@ equivalent file. .. code-block:: bash - alias bpython2.6='PYTHONPATH=~/python/bpython python2.6 -m bpython.cli' + alias bpython3.5='PYTHONPATH=~/python/bpython python3.5 -m bpython.cli' Where the `~/python/bpython`-path is the path to where your bpython source code resides. From e8d27bc7e2e8d04759b8d391a9370c9e26b2c527 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2016 23:38:26 +0100 Subject: [PATCH 0725/1650] Start 0.16 development Signed-off-by: Sebastian Ramacher --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 9496611ab..18f723893 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ Changelog ========= +0.16 +---- + 0.15 ---- From e09e0090baec7c37d76b141c55169271c9914ae4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2016 23:41:41 +0100 Subject: [PATCH 0726/1650] No longer run tests against Python 2.6 --- .travis.install.sh | 4 +--- .travis.yml | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index f1fd0c7f8..0417298e7 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -18,9 +18,7 @@ if [[ $RUN == nosetests ]]; then # translation specific dependencies pip install babel # Python 2.6 specific dependencies - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then - pip install unittest2 - elif [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then + if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then # dependencies for crasher tests pip install Twisted urwid fi diff --git a/.travis.yml b/.travis.yml index ab0053adb..6383194d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ notifications: - secure: "QXcEHVnOi5mZpONkHSu1tydj8EK3G7xJ7Wv/WYhJ5soNUpEJgi6YwR1WcxSjo7qyi8hTL+4jc+ID0TpKDeS1lpXF41kG9xf5kdxw5OL0EnMkrP9okUN0Ip8taEhd8w+6+dGmfZrx2nXOg1kBU7W5cE90XYqEtNDVXXgNeilT+ik=" python: - - "2.6" - "2.7" - "3.3" - "3.4" From 1202d14484760d30b44f7546add9d25180bfcc07 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2016 23:54:14 +0100 Subject: [PATCH 0727/1650] Remove 2.6 from documentation and setup Signed-off-by: Sebastian Ramacher --- .travis.install.sh | 2 +- bpython/repl.py | 2 +- doc/sphinx/source/contributing.rst | 2 +- doc/sphinx/source/releases.rst | 2 +- doc/sphinx/source/tips.rst | 4 ++-- setup.py | 15 ++++++--------- 6 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index 0417298e7..b73e9e5d6 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -17,7 +17,7 @@ if [[ $RUN == nosetests ]]; then pip install jedi # translation specific dependencies pip install babel - # Python 2.6 specific dependencies + # Python 2.7 specific dependencies if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then # dependencies for crasher tests pip install Twisted urwid diff --git a/bpython/repl.py b/bpython/repl.py index eee1c4759..cb1e3b052 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -436,7 +436,7 @@ def startup(self): with io.open(filename, 'rt', encoding=encoding) as f: source = f.read() if not py3: - # Python 2.6 and early 2.7.X need bytes. + # Early Python 2.7.X need bytes. source = source.encode(encoding) self.interp.runsource(source, filename, 'exec', encode=False) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 73e2e85fd..01cdca357 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -17,7 +17,7 @@ the time of day. Getting your development environment set up ------------------------------------------- -bpython supports Python 2.6, 2.7, 3.3 and 3.4. The code is compatible with all +bpython supports Python 2.7, 3.3 and newer. The code is compatible with all supported versions without the need to run post processing like `2to3`. Using a virtual environment is probably a good idea. Create a virtual diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst index 0a663843a..914360e7f 100644 --- a/doc/sphinx/source/releases.rst +++ b/doc/sphinx/source/releases.rst @@ -45,7 +45,7 @@ A checklist to perform some manual tests before a release: Check that all of the following work before a release: -* Runs under Python 2.6, 2.7, 3.3, 3.4 and 3.5. +* Runs under Python 2.7, 3.3, 3.4 and 3.5. * Save * Rewind * Pastebin diff --git a/doc/sphinx/source/tips.rst b/doc/sphinx/source/tips.rst index 60ea0a152..925097a87 100644 --- a/doc/sphinx/source/tips.rst +++ b/doc/sphinx/source/tips.rst @@ -21,8 +21,8 @@ equivalent file. Where the `~/python/bpython`-path is the path to where your bpython source code resides. -You can of course add multiple aliases, so you can run bpython with 2.6, 2.7 -and the 3 series. +You can of course add multiple aliases, so you can run bpython with 2.7 and the +3 series. .. note:: diff --git a/setup.py b/setup.py index db50b5f7a..94082134b 100755 --- a/setup.py +++ b/setup.py @@ -221,13 +221,12 @@ def initialize_options(self): 'watch': ['watchdog'], 'jedi': ['jedi'], # need requests[security] for SNI support (only before 2.7.7) - ':python_version == "2.6" or ' - 'python_full_version == "2.7.0" or ' \ - 'python_full_version == "2.7.1" or ' \ - 'python_full_version == "2.7.2" or ' \ - 'python_full_version == "2.7.3" or ' \ - 'python_full_version == "2.7.4" or ' \ - 'python_full_version == "2.7.5" or ' \ + ':python_full_version == "2.7.0" or ' + 'python_full_version == "2.7.1" or ' + 'python_full_version == "2.7.2" or ' + 'python_full_version == "2.7.3" or ' + 'python_full_version == "2.7.4" or ' + 'python_full_version == "2.7.5" or ' 'python_full_version == "2.7.6"': [ 'pyOpenSSL', 'pyasn1', @@ -254,8 +253,6 @@ def initialize_options(self): } tests_require = [] -if sys.version_info[0] == 2 and sys.version_info[1] < 7: - tests_require.append('unittest2') if (sys.version_info[0] == 2 or (sys.version_info[0] == 3 and sys.version_info[0] < 3)): tests_require.append('mock') From 2b08adfae97e9c11077635ac77a16718d24514e5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2016 23:56:59 +0100 Subject: [PATCH 0728/1650] Remove Python 2.6 workaround Signed-off-by: Sebastian Ramacher --- bpython/simpleeval.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 8e5141c4d..a337e8b2a 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -49,14 +49,6 @@ _name_type_nodes = (ast.Name,) -# inspect.isclass is broken in Python 2.6 -if sys.version_info[:2] == (2, 6): - def isclass(obj): - return isinstance(obj, (type, types.ClassType)) -else: - isclass = inspect.isclass - - class EvaluationError(Exception): """Raised if an exception occurred in safe_eval.""" @@ -277,7 +269,7 @@ def safe_get_attribute_new_style(obj, attr): if not is_new_style(obj): raise ValueError("%r is not a new-style class or object" % obj) to_look_through = (obj.__mro__ - if isclass(obj) + if inspect.isclass(obj) else (obj,) + type(obj).__mro__) for cls in to_look_through: From 686ebb422246ef7520e8e374605b94c7bf5a200e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2016 23:57:46 +0100 Subject: [PATCH 0729/1650] Document that 2.6 is no longer supported Signed-off-by: Sebastian Ramacher --- CHANGELOG | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 18f723893..67163d9f6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,14 @@ Changelog 0.16 ---- +New features: + +Fixes: + + + +Support for Python 2.6 has been dropped. + 0.15 ---- From b3692e6d3674f0ad9056f2e044d1d08b2ee9b95d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 Jan 2016 12:29:49 +0100 Subject: [PATCH 0730/1650] Finish CHANGELOG Signed-off-by: Sebastian Ramacher (cherry picked from commit fe49fc878a001f8a4e4c7fa418c5bda22c0d5996) --- CHANGELOG | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 67163d9f6..cbeabbd60 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,8 @@ Support for Python 2.6 has been dropped. 0.15 ---- +This release contains new features and plenty of bug fixes. + New features: * #425: Added curtsies 0.2.x support. @@ -26,24 +28,31 @@ New features: * Attribute completion works on literals and some expressions containing builtin objects. * Ctrl-e can be used to autocomplete current fish-style suggestion. - Thanks to Amjith Ramanujam + Thanks to Amjith Ramanujam. Fixes: * #484: Switch `bpython.embed` to the curtsies frontend. -* #548 Fix transpose character bug. Thanks to Wes E. Vial. +* #548 Fix transpose character bug. + Thanks to Wes E. Vial. * #527 -q disables version banner. -* #544 Fix Jedi completion error -* #536 Fix completion on old-style classes with custom __getattr__ -* #480 Fix old-style class autocompletion. Thanks to Joe Jevnik. -* #506 In python -i mod.py sys.modules[__name__] refers to module dict -* #590 Fix "None" not being displayed +* #544 Fix Jedi completion error. +* #536 Fix completion on old-style classes with custom __getattr__. +* #480 Fix old-style class autocompletion. + Thanks to Joe Jevnik. +* #506 In python -i mod.py sys.modules[__name__] refers to module dict. +* #590 Fix "None" not being displayed. * #546 Paste detection uses events instead of bytes returned in a single - os.read call -* Exceptions in autcompletion are now logged instead of crashing bpython. -* Fix reload in Python 3. Thanks to sharow. -* Fix keyword agument parameter name completion + os.read call. +* Exceptions in autocompletion are now logged instead of crashing bpython. +* Fix reload in Python 3. + Thanks to sharow. +* Fix keyword argument parameter name completion. + +Changes to dependencies: +* requests[security] has been changed to pyOpenSSL, pyasn1, and ndg-httpsclient. + These dependencies are required before Python 2.7.7. 0.14.2 ------ @@ -55,7 +64,6 @@ Fixes: * #523, #524: Fix conditional dependencies for SNI support again. * Fix binary name of bpdb. - 0.14.1 ------ From 4189d628cfb1048f447742060df84fc90f7afdf9 Mon Sep 17 00:00:00 2001 From: Aditya Gupta Date: Tue, 26 Jan 2016 00:12:45 +0530 Subject: [PATCH 0731/1650] Fixing Windows Python2 compatibility unsupported operand type | list and list: python 2.7 --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index 3d68b68ca..96dbbc972 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -281,7 +281,7 @@ def make_colors(config): } if platform.system() == 'Windows': - c = dict(c.items() | + c = dict(list(c.items()) + [ ('K', 8), ('R', 9), From ae12e15eaefa93e6c3933ea6a147038f67830904 Mon Sep 17 00:00:00 2001 From: bobf Date: Fri, 1 Apr 2016 08:42:23 +0100 Subject: [PATCH 0732/1650] Add Alternatives section to README.rst --- README.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.rst b/README.rst index 066a3631d..ed68c8cbc 100644 --- a/README.rst +++ b/README.rst @@ -165,6 +165,20 @@ To correct this I have provided a windows.theme file. This curses implementation has 16 colors (dark and light versions of the colours) + +============ +Alternatives +============ + +`ptpython`_ + +`IPython`_ + +Feel free to get in touch if you know of any other alternatives that people +may be interested to try. + +.. _ptpython: https://github.com/jonathanslenders/ptpython +.. _ipython: https://ipython.org/ .. _homepage: http://www.bpython-interpreter.org .. _full documentation: http://docs.bpython-interpreter.org/ .. _cffi docs: https://cffi.readthedocs.org/en/release-0.8/#macos-x From 21aa1fe2e053b9ad91356e5c05926ac7bd055104 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:25:33 +0000 Subject: [PATCH 0733/1650] spelling: accessed --- bpython/simpleeval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index a337e8b2a..d263111cc 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -224,7 +224,7 @@ def parse_trees(cursor_offset, line): def evaluate_current_attribute(cursor_offset, line, namespace=None): - """Safely evaluates the expression having an attributed accesssed""" + """Safely evaluates the expression having an attributed accessed""" # this function runs user code in case of custom descriptors, # so could fail in any way From f91e597e353f5e82d7fec0877d1846e8986779ec Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:26:23 +0000 Subject: [PATCH 0734/1650] spelling: attribute --- bpython/curtsiesfrontend/manual_readline.py | 2 +- bpython/test/test_simpleeval.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index a919df14d..053a729aa 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -56,7 +56,7 @@ def add(self, key, func, overwrite=False): def add_config_attr(self, config_attr, func): if config_attr in self.awaiting_config: - raise ValueError('config attrribute %r already has a mapping' % + raise ValueError('config attribute %r already has a mapping' % (config_attr,)) self.awaiting_config[config_attr] = func diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index d997fd912..ad7972f89 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -170,7 +170,7 @@ def __getattr__(self, attr): class OverridenGetattribute(object): def __getattribute__(self, attr): - raise AssertionError('custom __getattrribute__ executed') + raise AssertionError('custom __getattribute__ executed') a = 1 From 225c57a42f2640920453506d593b497ef22ab45b Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:26:33 +0000 Subject: [PATCH 0735/1650] spelling: autocomplete --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index 96dbbc972..4aea7edeb 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -449,7 +449,7 @@ def clear_wrapped_lines(self): self.scr.clrtoeol() def complete(self, tab=False): - """Get Autcomplete list and window. + """Get Autocomplete list and window. Called whenever these should be updated, and called with tab From d98a5fed90c3db1422b583627b19a63e9d6d1b23 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:27:12 +0000 Subject: [PATCH 0736/1650] spelling: capabilities --- doc/sphinx/source/themes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/themes.rst b/doc/sphinx/source/themes.rst index 99a9f0f63..e629b2d83 100644 --- a/doc/sphinx/source/themes.rst +++ b/doc/sphinx/source/themes.rst @@ -2,7 +2,7 @@ Themes ====== -This chapter is about bpython's theming capabalities. +This chapter is about bpython's theming capabilities. bpython uses .theme files placed in your ``$XDG_CONFIG_HOME/bpython`` directory [#f1]_. You can set the theme in the :ref:`configuration_color_scheme` option From 84413974d497ffc3ffaa8aa78126a65fc1e45018 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:27:25 +0000 Subject: [PATCH 0737/1650] spelling: classes --- bpython/simpleeval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index d263111cc..b7b348187 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -240,7 +240,7 @@ def evaluate_current_attribute(cursor_offset, line, namespace=None): def safe_get_attribute(obj, attr): - """Gets attributes without triggering descriptors on new-style clases""" + """Gets attributes without triggering descriptors on new-style classes""" if is_new_style(obj): with AttrCleaner(obj): result = safe_get_attribute_new_style(obj, attr) From 0f5dff3e4a439c4af9313ec24b62131d38907854 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:27:48 +0000 Subject: [PATCH 0738/1650] spelling: copyright --- bpython/__main__.py | 2 +- bpython/history.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/__main__.py b/bpython/__main__.py index a452a6364..fe2651ebc 100644 --- a/bpython/__main__.py +++ b/bpython/__main__.py @@ -2,7 +2,7 @@ # The MIT License # -# Copyirhgt (c) 2015 Sebastian Ramacher +# Copyright (c) 2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/bpython/history.py b/bpython/history.py index e08ec2684..4e63bf776 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -3,7 +3,7 @@ # The MIT License # # Copyright (c) 2009 the bpython authors. -# Copyirhgt (c) 2012,2015 Sebastian Ramacher +# Copyright (c) 2012,2015 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal From 796f7e2d2c02825b2459155b8ce1933e567bb449 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:28:45 +0000 Subject: [PATCH 0739/1650] spelling: didn't --- bpython/test/test_curtsies_painting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index df3507369..7e7099e9c 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -539,7 +539,7 @@ def test_cursor_stays_at_bottom_of_screen(self): self.assert_paint_ignoring_formatting(screen, (2, 4)) def test_unhighlight_paren_bugs(self): - """two previous bugs, paren did't highlight until next render + """two previous bugs, paren didn't highlight until next render and paren didn't unhighlight until enter""" self.assertEqual(self.repl.rl_history.entries, ['']) self.enter('(') From d3151baecd5ad283fc285fa9adc37936f82df683 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:29:12 +0000 Subject: [PATCH 0740/1650] spelling: docstring --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index cbeabbd60..5825c0e7f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -377,7 +377,7 @@ As always, please submit any bugs you might find to our bugtracker. * #87: Add a closed attribute to Repl to fix mercurial.ui.ui expecting stderr to have this attribute. -* #108: Unicode characters in docsrting crash bpython +* #108: Unicode characters in docstring crash bpython * #118: Load_theme is not defined. * #99: Configurable font now documented. * #123: Pastebin can't handle 'ESC' key From 953bdda3dfa5c3afa51dd3dafcfbee01685546cd Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:29:53 +0000 Subject: [PATCH 0741/1650] spelling: height --- bpython/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 4aea7edeb..b2993957a 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1396,7 +1396,7 @@ def lsize(): def size(self): """Set instance attributes for x and y top left corner coordinates - and width and heigth for the window.""" + and width and height for the window.""" global stdscr h, w = stdscr.getmaxyx() self.y = 0 @@ -1566,7 +1566,7 @@ def __init__(self, scr, pwin, background, config, s=None, c=None): def size(self): """Set instance attributes for x and y top left corner coordinates - and width and heigth for the window.""" + and width and height for the window.""" h, w = gethw() self.y = h - 1 self.w = w From c2ddc7febd94c404057cc2efcd88cd74f94c88d4 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:30:06 +0000 Subject: [PATCH 0742/1650] spelling: implementation --- bpython/clipboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/clipboard.py b/bpython/clipboard.py index a37572a56..252f1a225 100644 --- a/bpython/clipboard.py +++ b/bpython/clipboard.py @@ -60,7 +60,7 @@ def command_exists(command): def get_clipboard(): - """Get best clipboard handling implemention for current system.""" + """Get best clipboard handling implementation for current system.""" if platform.system() == 'Darwin': if command_exists('pbcopy'): From d981f75d0ed96847017aaa95c2158304c522c495 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:30:34 +0000 Subject: [PATCH 0743/1650] spelling: interpreter --- bpython/curtsiesfrontend/repl.py | 2 +- bpython/repl.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 54ec150a0..5d9d55e3e 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1136,7 +1136,7 @@ def send_to_stdout(self, output): def send_to_stderr(self, error): """Send unicode strings or FmtStr to Repl stderr - Must be able to handle FmtStrs because interepter pass in + Must be able to handle FmtStrs because interpreter pass in tracebacks already formatted.""" lines = error.split('\n') if lines[-1]: diff --git a/bpython/repl.py b/bpython/repl.py index cb1e3b052..0e1fbaadc 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -83,7 +83,7 @@ def __init__(self, locals=None, encoding=None): on a caught syntax error. The purpose for this in bpython is so that the repl can be instantiated after the interpreter (which it necessarily must be with the current factoring) and then an exception - callback can be added to the Interpeter instance afterwards - more + callback can be added to the Interpreter instance afterwards - more specifically, this is so that autoindentation does not occur after a traceback.""" From 19e474ee5bc9497dfdf4b2f734e975d8b09d56ba Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:31:50 +0000 Subject: [PATCH 0744/1650] spelling: multiline --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5825c0e7f..8449f5380 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -101,7 +101,7 @@ New features: * #354: Edit config file from within bpython. * #382: Partial support for pasting in text with blank lines. * #410: Startup banner that shows Python and bpython version -* #426: Experimental mutliline autocompletion. +* #426: Experimental multiline autocompletion. * fish style last history completion with Arrow Right. Thanks to Nicholas Sweeting. * fish style automatic reverse history search with Arrow Up. From 150db0bc8f8ce712faac30c8eb19a82118cc2a8f Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:32:07 +0000 Subject: [PATCH 0745/1650] spelling: occurring --- bpython/cli.py | 2 +- bpython/repl.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index b2993957a..88ce70560 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -434,7 +434,7 @@ def check(self): def clear_current_line(self): """Called when a SyntaxError occurred in the interpreter. It is - used to prevent autoindentation from occuring after a + used to prevent autoindentation from occurring after a traceback.""" repl.Repl.clear_current_line(self) self.s = '' diff --git a/bpython/repl.py b/bpython/repl.py index 0e1fbaadc..3f1e3d5c4 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1023,7 +1023,7 @@ def tokenize(self, s, newline=False): def clear_current_line(self): """This is used as the exception callback for the Interpreter instance. - It prevents autoindentation from occuring after a traceback.""" + It prevents autoindentation from occurring after a traceback.""" def send_to_external_editor(self, text, filename=None): """Returns modified text from an editor, or the oriignal text if editor From 106cfde271ec2422975b14fd8d9db99f04022fc1 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:32:17 +0000 Subject: [PATCH 0746/1650] spelling: original --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 3f1e3d5c4..86f4a5642 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1026,7 +1026,7 @@ def clear_current_line(self): It prevents autoindentation from occurring after a traceback.""" def send_to_external_editor(self, text, filename=None): - """Returns modified text from an editor, or the oriignal text if editor + """Returns modified text from an editor, or the original text if editor exited with non-zero""" encoding = getpreferredencoding() From 449084c56400b4720a55e52d8f239f94c1cb5c40 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:32:36 +0000 Subject: [PATCH 0747/1650] spelling: overridden --- bpython/test/test_simpleeval.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index ad7972f89..9db2fbf9b 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -162,19 +162,19 @@ def s4(self): raise AssertionError('Property __get__ executed') -class OverridenGetattr(object): +class OverriddenGetattr(object): def __getattr__(self, attr): raise AssertionError('custom __getattr__ executed') a = 1 -class OverridenGetattribute(object): +class OverriddenGetattribute(object): def __getattribute__(self, attr): raise AssertionError('custom __getattribute__ executed') a = 1 -class OverridenMRO(object): +class OverriddenMRO(object): def __mro__(self): raise AssertionError('custom mro executed') a = 1 @@ -236,17 +236,17 @@ def test_lookup_with_property_and_slots(self): Slots.__dict__['s3']) self.assertIsInstance(sga(SlotsSubclass, 's3'), property) - def test_lookup_on_overriden_methods(self): + def test_lookup_on_overridden_methods(self): sga = safe_get_attribute - self.assertEqual(sga(OverridenGetattr(), 'a'), 1) - self.assertEqual(sga(OverridenGetattribute(), 'a'), 1) - self.assertEqual(sga(OverridenMRO(), 'a'), 1) + self.assertEqual(sga(OverriddenGetattr(), 'a'), 1) + self.assertEqual(sga(OverriddenGetattribute(), 'a'), 1) + self.assertEqual(sga(OverriddenMRO(), 'a'), 1) with self.assertRaises(AttributeError): - sga(OverridenGetattr(), 'b') + sga(OverriddenGetattr(), 'b') with self.assertRaises(AttributeError): - sga(OverridenGetattribute(), 'b') + sga(OverriddenGetattribute(), 'b') with self.assertRaises(AttributeError): - sga(OverridenMRO(), 'b') + sga(OverriddenMRO(), 'b') if __name__ == '__main__': unittest.main() From b561767933b943064c1d2e2dd5e70ed450a24e62 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:28:58 +0000 Subject: [PATCH 0748/1650] spelling: parent --- bpython/test/test_curtsies_painting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 7e7099e9c..87af5be62 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -539,7 +539,7 @@ def test_cursor_stays_at_bottom_of_screen(self): self.assert_paint_ignoring_formatting(screen, (2, 4)) def test_unhighlight_paren_bugs(self): - """two previous bugs, paren didn't highlight until next render + """two previous bugs, parent didn't highlight until next render and paren didn't unhighlight until enter""" self.assertEqual(self.repl.rl_history.entries, ['']) self.enter('(') From dc8563e6c5f128336625ed4e883b5c25e5cfb0c0 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:33:33 +0000 Subject: [PATCH 0749/1650] spelling: preceded --- bpython/formatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/formatter.py b/bpython/formatter.py index 557ecda5e..6c0ab75ef 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -31,7 +31,7 @@ """These format strings are pretty ugly. \x01 represents a colour marker, which - can be proceded by one or two of + can be preceded by one or two of the following letters: k, r, g, y, b, m, c, w, d Which represent: From bfe3ac2a54cfd35bc0616e65d2184e126a68e214 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:33:08 +0000 Subject: [PATCH 0750/1650] spelling: prerequisites --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index 88ce70560..959beb6a3 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -24,7 +24,7 @@ # Modified by Brandon Navra # Notes for Windows -# Prerequsites +# Prerequisites # - Curses # - pyreadline # From e72b7256e3093e9fb31fd90bcaf2d9151c549794 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:33:54 +0000 Subject: [PATCH 0751/1650] spelling: returns --- bpython/test/test_crashers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 1aea547e7..e2f1ccabb 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -49,7 +49,7 @@ def run_bpython(self, input): enter the given input. Uses a test config that disables the paste detection. - Retuns bpython's output. + Returns bpython's output. """ result = Deferred() From e52b62fd1bbbbddad8a707bb21cfbd6fc944f309 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:34:22 +0000 Subject: [PATCH 0752/1650] spelling: suggestion --- data/bpython.appdata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/bpython.appdata.xml b/data/bpython.appdata.xml index e3d5d32c9..835020527 100644 --- a/data/bpython.appdata.xml +++ b/data/bpython.appdata.xml @@ -13,7 +13,7 @@ following features:
  • In-line syntac highlighting.
  • -
  • Readline-like autocomplete with suggesstion displayed as you type.
  • +
  • Readline-like autocomplete with suggestion displayed as you type.
  • Expected parameter list for any Python function.
  • "Rewind" function to pop the last line of code from memory and re-evaluate.
  • Send the code you've entered off to a pastebin.
  • From 88bf1b1429e9322a504bf071acdd1acadf5e85c2 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:34:27 +0000 Subject: [PATCH 0753/1650] spelling: supports --- bpython/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index 288bfb824..bd78c399b 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -33,7 +33,7 @@ def can_encode(c): def supports_box_chars(): - """Check if the encoding suppors Unicode box characters.""" + """Check if the encoding supports Unicode box characters.""" return all(map(can_encode, u'│─└┘┌┐')) From cd73c8a62cd5c4c95dd99a6505b8bfe7d9f2d177 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:34:38 +0000 Subject: [PATCH 0754/1650] spelling: syntax --- data/bpython.appdata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/bpython.appdata.xml b/data/bpython.appdata.xml index 835020527..721e56e98 100644 --- a/data/bpython.appdata.xml +++ b/data/bpython.appdata.xml @@ -12,7 +12,7 @@ bpython is a fancy interface to the Python interpreter. It has the following features:
      -
    • In-line syntac highlighting.
    • +
    • In-line syntax highlighting.
    • Readline-like autocomplete with suggestion displayed as you type.
    • Expected parameter list for any Python function.
    • "Rewind" function to pop the last line of code from memory and re-evaluate.
    • From f2014dbae31313571cc9c26f51a14f4fda09d138 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:35:33 +0000 Subject: [PATCH 0755/1650] spelling: unknown --- bpython/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/__init__.py b/bpython/__init__.py index 8d68d209c..0fd94818f 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -25,7 +25,7 @@ try: from bpython._version import __version__ as version except ImportError: - version = 'unkown' + version = 'unknown' __version__ = version package_dir = os.path.abspath(os.path.dirname(__file__)) From 8f5211e3f618c5883f17cd92ac32f1b329edfb1c Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:35:56 +0000 Subject: [PATCH 0756/1650] spelling: unnecessary --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index 959beb6a3..c1e3ded46 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1375,7 +1375,7 @@ def lsize(): get_colpair(self.config, 'comment')) # XXX: After all the trouble I had with sizing the list box (I'm not very good # at that type of thing) I decided to do this bit of tidying up here just to - # make sure there's no unnececessary blank lines, it makes things look nicer. + # make sure there's no unnecessary blank lines, it makes things look nicer. y = self.list_win.getyx()[0] self.list_win.resize(y + 2, w) From 92ab244002b65438791b5d85a1bc3a262f0af45d Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:36:15 +0000 Subject: [PATCH 0757/1650] spelling: writing --- doc/sphinx/source/themes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/themes.rst b/doc/sphinx/source/themes.rst index e629b2d83..86773609f 100644 --- a/doc/sphinx/source/themes.rst +++ b/doc/sphinx/source/themes.rst @@ -20,7 +20,7 @@ Available Colors * w = white * d = default, this will make the switch default to the bpython default theme -Any letter writting uppercase will make the switch bold. +Any letter writing uppercase will make the switch bold. Available Switches ------------------ From eb91dcf1dee211bbb796cff4cea31bf49fc53783 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Thu, 7 Apr 2016 16:35:15 +0000 Subject: [PATCH 0758/1650] grammar: have --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 8449f5380..cdc61254a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -522,7 +522,7 @@ Probably some other things, but I hate changelogs. :) 0.8.0 ------ -It's been a long while since the last release and there've been numerous little +It's been a long while since the last release and there have been numerous little bugfixes and extras here and there so I'm putting this out as 0.8.0. Check the hg commit history if you want more info: http://bitbucket.org/bobf/bpython/ From 302bf4928e2e9001edaba2d12fd57106218a2389 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Tue, 19 Apr 2016 11:51:47 -0700 Subject: [PATCH 0759/1650] Fix DELETE key not being recognized curtsies stopped supporting PADDELETE at this point: https://github.com/thomasballinger/curtsies/commit/415016436eb30411acc1a4725aceaab6c00d58c7 fixes #609 --- bpython/curtsiesfrontend/manual_readline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 053a729aa..3849a95b8 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -212,7 +212,7 @@ def back_word(cursor_offset, line): return (last_word_pos(line[:cursor_offset]), line) -@edit_keys.on('') +@edit_keys.on('') def delete(cursor_offset, line): return (cursor_offset, line[:cursor_offset] + line[cursor_offset+1:]) From 325d3bbdee1d9d8394cbd0e166e85969eeb4d194 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 31 May 2016 20:05:37 +0200 Subject: [PATCH 0760/1650] Fix TypeError when using F2 without some source File "/usr/lib/python3.5/site-packages/bpython/cli.py", line 977, in p_key self.statusbar.message(_(e)) File "/usr/lib/python3.5/site-packages/bpython/cli.py", line 1605, in message self.settext(s) File "/usr/lib/python3.5/site-packages/bpython/cli.py", line 1655, in settext if len(s) >= self.w: TypeError: object of type 'SourceNotFound' has no len() This also adjust the string formatting in `bpython/curtsiesfrontend/repl.py`. --- bpython/cli.py | 2 +- bpython/curtsiesfrontend/repl.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index c1e3ded46..4ca5bef85 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -974,7 +974,7 @@ def p_key(self, key): try: source = self.get_source_of_current_name() except repl.SourceNotFound as e: - self.statusbar.message(_(e)) + self.statusbar.message(str(e)) else: if config.highlight_show_source: source = format(PythonLexer().get_tokens(source), diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 5d9d55e3e..98aa59d24 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1682,7 +1682,7 @@ def show_source(self): try: source = self.get_source_of_current_name() except SourceNotFound as e: - self.status_bar.message('%s' % (e, )) + self.status_bar.message(str(e)) else: if self.config.highlight_show_source: source = format(PythonLexer().get_tokens(source), From c2c9e84f491073fe3bb7df88febc0c14247e768b Mon Sep 17 00:00:00 2001 From: Caleb Stewart Date: Sat, 4 Jun 2016 13:45:05 -0500 Subject: [PATCH 0761/1650] Added fileno support to FakeOutput --- bpython/curtsiesfrontend/coderunner.py | 12 +++++++++++- bpython/curtsiesfrontend/repl.py | 5 +++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 21fdbc18a..79fb215ff 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -195,13 +195,17 @@ def request_from_main_greenlet(self, force_refresh=False): class FakeOutput(object): - def __init__(self, coderunner, on_write): + def __init__(self, coderunner, on_write, fileno=1): """Fakes sys.stdout or sys.stderr on_write should always take unicode + + fileno should be the fileno that on_write will + output to (e.g. 1 for standard output). """ self.coderunner = coderunner self.on_write = on_write + self.real_fileno = fileno def write(self, s, *args, **kwargs): if not py3 and isinstance(s, str): @@ -209,6 +213,12 @@ def write(self, s, *args, **kwargs): self.on_write(s, *args, **kwargs) return self.coderunner.request_from_main_greenlet(force_refresh=True) + # Some applications which use curses require that sys.stdout + # have a method called fileno. One example is pwntools. This + # is not a widespread issue, but is annoying. + def fileno(self): + return self.real_fileno + def writelines(self, l): for s in l: self.write(s) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 98aa59d24..b3777bc94 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -389,8 +389,9 @@ def __init__(self, self.orig_tcattrs = orig_tcattrs self.coderunner = CodeRunner(self.interp, self.request_refresh) - self.stdout = FakeOutput(self.coderunner, self.send_to_stdout) - self.stderr = FakeOutput(self.coderunner, self.send_to_stderr) + # Added explicit fileno definitions to reflect to backend device + self.stdout = FakeOutput(self.coderunner, self.send_to_stdout, fileno=1) + self.stderr = FakeOutput(self.coderunner, self.send_to_stderr, fileno=2) self.stdin = FakeStdin(self.coderunner, self, self.edit_keys) # next paint should clear screen From 27c041ffcb2101117794286181a433ec3a490824 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 28 Jul 2016 21:51:44 -0400 Subject: [PATCH 0762/1650] commit test --- bpython/repl.py | 8 +++++++- bpython/test/test_args.py | 25 +++++++++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 86f4a5642..1cc315561 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -85,7 +85,13 @@ def __init__(self, locals=None, encoding=None): necessarily must be with the current factoring) and then an exception callback can be added to the Interpreter instance afterwards - more specifically, this is so that autoindentation does not occur after a - traceback.""" + traceback. + + encoding is only used in Python 2, where it may be necessary to add an + encoding comment to a source bytestring before running it. + encoding must be a bytestring in Python 2 because it will be templated + into a bytestring source as part of an encoding comment. + """ self.encoding = encoding or sys.getdefaultencoding() self.syntaxerror_callback = None diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index b85eda7d0..25be01bef 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -1,15 +1,12 @@ +# encoding: utf-8 + import subprocess import sys import tempfile from textwrap import dedent from bpython import args -from bpython.test import FixLanguageTestCase as TestCase - -try: - import unittest2 as unittest -except ImportError: - import unittest +from bpython.test import (FixLanguageTestCase as TestCase, unittest) try: from nose.plugins.attrib import attr @@ -40,6 +37,22 @@ def test_exec_dunder_file(self): self.assertEquals(stderr.strip(), f.name) + def test_exec_nonascii_file(self): + with tempfile.NamedTemporaryFile(mode="w") as f: + f.write(dedent('''\ + #!/usr/bin/env python2 + # coding: utf-8 + "你好 # nonascii" + ''')) + f.flush() + try: + subprocess.check_call([ + 'python', '-m', 'bpython.curtsies', + f.name]) + except subprocess.CalledProcessError: + self.fail('Error running module with nonascii characters') + + class TestParse(TestCase): def test_version(self): From dfb44cf97abddd684229b5ba3531912904553be0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 28 Jul 2016 21:53:23 -0400 Subject: [PATCH 0763/1650] fix Python 2 running encoded source files --- bpython/inspection.py | 10 +++++++++ bpython/repl.py | 35 ++++++++++++++++++++++++-------- bpython/test/test_interpreter.py | 12 +++++------ 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index c14bfdc9b..b4d9bf4e0 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -284,6 +284,7 @@ def is_callable(obj): get_encoding_re = LazyReCompile(r'coding[:=]\s*([-\w.]+)') +get_encoding_line_re = LazyReCompile(r'^.*coding[:=]\s*[-\w.]+.*$') def get_encoding(obj): @@ -295,6 +296,15 @@ def get_encoding(obj): return 'ascii' +def get_encoding_comment(source): + """Returns encoding line without the newline, or None is not found""" + for line in source.splitlines()[:2]: + m = get_encoding_line_re.search(line) + if m: + return m.group(0) + return None + + def get_encoding_file(fname): """Try to obtain encoding information from a Python source file.""" with io.open(fname, 'rt', encoding='ascii', errors='ignore') as f: diff --git a/bpython/repl.py b/bpython/repl.py index 1cc315561..ea2ffdc6d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -109,10 +109,30 @@ def runsource(self, source, filename=None, symbol='single', source, filename and symbol are passed on to code.InteractiveInterpreter.runsource. If encode is True, the source - will be encoded. On Python 3.X, encode will be ignored.""" - if not py3 and encode: - source = u'# coding: %s\n\n%s' % (self.encoding, source) - source = source.encode(self.encoding) + will be encoded. On Python 3.X, encode will be ignored. + + encode doesn't encode the source, it just adds an encoding comment + that specifies the encoding of the source. + encode should only be used for interactive interpreter input, + files should always have an encoding comment or be ASCII. + + In Python 3, source must be a unicode string + In Python 2, source may be latin-1 bytestring or unicode string, + following the interface of code.InteractiveInterpreter""" + if encode and not py3: + if isinstance(source, str): + # encoding only makes sense for bytestrings + assert isinstance(source, str) + source = b'# coding: %s\n\n%s' % (self.encoding, source) + else: + # 2 blank lines still need to be added because this + # interpreter always adds 2 lines to stack trace line + # numbers in Python 2 + comment = inspection.get_encoding_comment(source) + if comment: + source = source.replace(comment, u'%s\n\n' % comment, 1) + else: + source = u'\n\n' + source if filename is None: filename = filename_for_console_input(source) with self.timer: @@ -138,11 +158,11 @@ def showsyntaxerror(self, filename=None): pass else: # Stuff in the right filename and right lineno - if not py3: - lineno -= 2 # strip linecache line number if re.match(r'', filename): filename = '' + if filename == '' and not py3: + lineno -= 2 value = SyntaxError(msg, (filename, lineno, offset, line)) sys.last_value = value list = traceback.format_exception_only(type, value) @@ -166,8 +186,7 @@ def showtraceback(self): fname = '' tblist[i] = (fname, lineno, module, something) # Set the right lineno (encoding header adds an extra line) - if not py3: - if fname == '': + if fname == '' and not py3: tblist[i] = (fname, lineno - 2, module, something) l = traceback.format_list(tblist) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 3a6de8d62..fc500f259 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -50,7 +50,7 @@ def append_to_a(message): i.write = append_to_a def f(): - return 1/0 + return 1 / 0 def g(): return f() @@ -73,7 +73,7 @@ def g(): @unittest.skipIf(py3, "runsource() accepts only unicode in Python 3") def test_runsource_bytes(self): - i = interpreter.Interp(encoding='latin-1') + i = interpreter.Interp(encoding=b'latin-1') i.runsource("a = b'\xfe'".encode('latin-1'), encode=False) self.assertIsInstance(i.locals['a'], str) @@ -85,7 +85,7 @@ def test_runsource_bytes(self): @unittest.skipUnless(py3, "Only a syntax error in Python 3") def test_runsource_bytes_over_128_syntax_error_py3(self): - i = interpreter.Interp(encoding='latin-1') + i = interpreter.Interp(encoding=b'latin-1') i.showsyntaxerror = mock.Mock(return_value=None) i.runsource("a = b'\xfe'", encode=True) @@ -93,15 +93,15 @@ def test_runsource_bytes_over_128_syntax_error_py3(self): @unittest.skipIf(py3, "encode is Python 2 only") def test_runsource_bytes_over_128_syntax_error_py2(self): - i = interpreter.Interp(encoding='latin-1') + i = interpreter.Interp(encoding=b'latin-1') - i.runsource("a = b'\xfe'", encode=True) + i.runsource(b"a = b'\xfe'", encode=True) self.assertIsInstance(i.locals['a'], type(b'')) self.assertEqual(i.locals['a'], b"\xfe") @unittest.skipIf(py3, "encode is Python 2 only") def test_runsource_unicode(self): - i = interpreter.Interp(encoding='latin-1') + i = interpreter.Interp(encoding=b'latin-1') i.runsource("a = u'\xfe'", encode=True) self.assertIsInstance(i.locals['a'], type(u'')) From df29ae19ed89c5336252f20b4974046deb5bd534 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 28 Jul 2016 22:40:42 -0400 Subject: [PATCH 0764/1650] correct linenums for external files --- bpython/repl.py | 14 ++++++++------ bpython/test/test_args.py | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index ea2ffdc6d..f00784e0c 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -104,21 +104,23 @@ def reset_running_time(self): self.running_time = 0 def runsource(self, source, filename=None, symbol='single', - encode=True): + encode='auto'): """Execute Python code. source, filename and symbol are passed on to - code.InteractiveInterpreter.runsource. If encode is True, the source - will be encoded. On Python 3.X, encode will be ignored. + code.InteractiveInterpreter.runsource. If encode is True, + an encoding comment will be added to the source. + On Python 3.X, encode will be ignored. - encode doesn't encode the source, it just adds an encoding comment - that specifies the encoding of the source. encode should only be used for interactive interpreter input, - files should always have an encoding comment or be ASCII. + files should always already have an encoding comment or be ASCII. + By default an encoding line will be added if no filename is given. In Python 3, source must be a unicode string In Python 2, source may be latin-1 bytestring or unicode string, following the interface of code.InteractiveInterpreter""" + if encode == 'auto': + encode = filename is None if encode and not py3: if isinstance(source, str): # encoding only makes sense for bytestrings diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 25be01bef..20438d4e3 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -36,7 +36,6 @@ def test_exec_dunder_file(self): self.assertEquals(stderr.strip(), f.name) - def test_exec_nonascii_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write(dedent('''\ @@ -52,6 +51,23 @@ def test_exec_nonascii_file(self): except subprocess.CalledProcessError: self.fail('Error running module with nonascii characters') + def test_exec_nonascii_file_linenums(self): + with tempfile.NamedTemporaryFile(mode="w") as f: + f.write(dedent("""\ + #!/usr/bin/env python2 + # coding: utf-8 + 1/0 + """)) + f.flush() + p = subprocess.Popen( + [sys.executable, "-m", "bpython.curtsies", + f.name], + stderr=subprocess.PIPE, + universal_newlines=True) + (_, stderr) = p.communicate() + + self.assertIn('line 3', stderr) + class TestParse(TestCase): From ebeffd714c0406b724adf27717c0f464eef88277 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 28 Jul 2016 22:51:21 -0400 Subject: [PATCH 0765/1650] fix test for early 2.7 --- bpython/test/test_args.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 20438d4e3..9c89f73c9 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -1,5 +1,6 @@ # encoding: utf-8 +import re import subprocess import sys import tempfile @@ -66,7 +67,11 @@ def test_exec_nonascii_file_linenums(self): universal_newlines=True) (_, stderr) = p.communicate() - self.assertIn('line 3', stderr) + self.assertIn('line 3', clean_colors(stderr)) + + +def clean_colors(s): + return re.sub(r'\x1b[^m]*m', '', s) class TestParse(TestCase): From a2a934cc2b4f8d93c4b2fee0e6b542dd42dabf9a Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 4 Aug 2016 08:58:59 -0400 Subject: [PATCH 0766/1650] add auto default to runsource --- bpython/repl.py | 50 ++++++---- bpython/test/test_interpreter.py | 157 ++++++++++++++++++++++++++++--- 2 files changed, 177 insertions(+), 30 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index f00784e0c..cd3dc9a5c 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -118,23 +118,41 @@ def runsource(self, source, filename=None, symbol='single', In Python 3, source must be a unicode string In Python 2, source may be latin-1 bytestring or unicode string, - following the interface of code.InteractiveInterpreter""" - if encode == 'auto': - encode = filename is None - if encode and not py3: - if isinstance(source, str): - # encoding only makes sense for bytestrings - assert isinstance(source, str) - source = b'# coding: %s\n\n%s' % (self.encoding, source) - else: - # 2 blank lines still need to be added because this - # interpreter always adds 2 lines to stack trace line + following the interface of code.InteractiveInterpreter. + + Because adding an encoding comment to a unicode string in Python 2 + would cause a syntax error to be thrown which would reference code + the user did not write, setting encoding to True when source is a + unicode string in Python 2 will throw a ValueError.""" + # str means bytestring in Py2 + if encode and not py3 and isinstance(source, unicode): + if encode != 'auto': + raise ValueError("can't add encoding line to unicode input") + encode = False + if encode and filename is not None: + # files have encoding comments or implicit encoding of ASCII + if encode != 'auto': + raise ValueError("shouldn't add encoding line to file contents") + encode = False + + if encode and not py3 and isinstance(source, str): + # encoding makes sense for bytestrings, so long as there + # isn't already an encoding comment + comment = inspection.get_encoding_comment(source) + if comment: + # keep the existing encoding comment, but add two lines + # because this interp always adds 2 to stack trace line # numbers in Python 2 - comment = inspection.get_encoding_comment(source) - if comment: - source = source.replace(comment, u'%s\n\n' % comment, 1) - else: - source = u'\n\n' + source + source = source.replace(comment, b'%s\n\n' % comment, 1) + else: + source = b'# coding: %s\n\n%s' % (self.encoding, source) + elif not py3 and filename is None: + # 2 blank lines still need to be added + # because this interpreter always adds 2 to stack trace line + # numbers in Python 2 when the filename is "" + newlines = u'\n\n' if isinstance(source, unicode) else b'\n\n' + source = newlines + source + # we know we're in Python 2 here, so ok to reference unicode if filename is None: filename = filename_for_console_input(source) with self.timer: diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index fc500f259..7adf7297b 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals import sys +import re +from textwrap import dedent from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain @@ -13,15 +15,32 @@ pypy = 'PyPy' in sys.version +def remove_ansi(s): + return re.sub(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]'.encode('ascii'), b'', s) + + class TestInterpreter(unittest.TestCase): - def test_syntaxerror(self): + def interp_errlog(self): i = interpreter.Interp() a = [] + i.write = a.append + return i, a + + def err_lineno(self, a): + strings = [x.__unicode__() for x in a] + print('looking for lineno') + for line in reversed(strings): + clean_line = remove_ansi(line) + print(clean_line) + m = re.search(r'line (\d+)[,]', clean_line) + if m: + print('found!', m.group(1)) + return int(m.group(1)) + return None - def append_to_a(message): - a.append(message) + def test_syntaxerror(self): + i, a = self.interp_errlog() - i.write = append_to_a i.runsource('1.1.1.1') if pypy: @@ -41,13 +60,7 @@ def append_to_a(message): self.assertEquals(plain('').join(a), expected) def test_traceback(self): - i = interpreter.Interp() - a = [] - - def append_to_a(message): - a.append(message) - - i.write = append_to_a + i, a = self.interp_errlog() def f(): return 1 / 0 @@ -88,14 +101,14 @@ def test_runsource_bytes_over_128_syntax_error_py3(self): i = interpreter.Interp(encoding=b'latin-1') i.showsyntaxerror = mock.Mock(return_value=None) - i.runsource("a = b'\xfe'", encode=True) + i.runsource("a = b'\xfe'") i.showsyntaxerror.assert_called_with(mock.ANY) @unittest.skipIf(py3, "encode is Python 2 only") def test_runsource_bytes_over_128_syntax_error_py2(self): i = interpreter.Interp(encoding=b'latin-1') - i.runsource(b"a = b'\xfe'", encode=True) + i.runsource(b"a = b'\xfe'") self.assertIsInstance(i.locals['a'], type(b'')) self.assertEqual(i.locals['a'], b"\xfe") @@ -103,7 +116,7 @@ def test_runsource_bytes_over_128_syntax_error_py2(self): def test_runsource_unicode(self): i = interpreter.Interp(encoding=b'latin-1') - i.runsource("a = u'\xfe'", encode=True) + i.runsource("a = u'\xfe'") self.assertIsInstance(i.locals['a'], type(u'')) self.assertEqual(i.locals['a'], u"\xfe") @@ -114,3 +127,119 @@ def test_getsource_works_on_interactively_defined_functions(self): import inspect inspected_source = inspect.getsource(i.locals['foo']) self.assertEquals(inspected_source, source) + + @unittest.skipIf(py3, "encode only does anything in Python 2") + def test_runsource_unicode_autoencode_and_noencode(self): + """error line numbers should be fixed""" + + # Since correct behavior for unicode is the same + # for auto and False, run the same tests + for encode in ['auto', False]: + i, a = self.interp_errlog() + i.runsource(u'[1 + 1,\nabc]', encode=encode) + self.assertEqual(self.err_lineno(a), 2) + + i, a = self.interp_errlog() + i.runsource(u'[1 + 1,\nabc]', encode=encode) + self.assertEqual(self.err_lineno(a), 2) + + i, a = self.interp_errlog() + i.runsource(u'#encoding: utf-8\nabc', encode=encode) + self.assertEqual(self.err_lineno(a), 2) + + i, a = self.interp_errlog() + i.runsource(u'#encoding: utf-8\nabc', + filename='x.py', encode=encode) + self.assertIn('SyntaxError: encoding', + ''.join(''.join(remove_ansi(x.__unicode__()) for x in a))) + + @unittest.skipIf(py3, "encode only does anything in Python 2") + def test_runsource_unicode_encode(self): + i, _ = self.interp_errlog() + with self.assertRaises(ValueError): + i.runsource(u'1 + 1', encode=True) + + i, _ = self.interp_errlog() + with self.assertRaises(ValueError): + i.runsource(u'1 + 1', filename='x.py', encode=True) + + @unittest.skipIf(py3, "encode only does anything in Python 2") + def test_runsource_bytestring_noencode(self): + i, a = self.interp_errlog() + i.runsource(b'[1 + 1,\nabc]', encode=False) + self.assertEqual(self.err_lineno(a), 2) + + i, a = self.interp_errlog() + i.runsource(b'[1 + 1,\nabc]', filename='x.py', encode=False) + self.assertEqual(self.err_lineno(a), 2) + + i, a = self.interp_errlog() + i.runsource(dedent(b'''\ + #encoding: utf-8 + + ["%s", + abc]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=False) + self.assertEqual(self.err_lineno(a), 4) + + i, a = self.interp_errlog() + i.runsource(dedent(b'''\ + #encoding: utf-8 + + ["%s", + abc]''' % (u'åß∂ƒ'.encode('utf8'),)), + filename='x.py', encode=False) + self.assertEqual(self.err_lineno(a), 4) + + @unittest.skipIf(py3, "encode only does anything in Python 2") + def test_runsource_bytestring_encode(self): + i, a = self.interp_errlog() + i.runsource(b'[1 + 1,\nabc]', encode=True) + self.assertEqual(self.err_lineno(a), 2) + + i, a = self.interp_errlog() + with self.assertRaises(ValueError): + i.runsource(b'[1 + 1,\nabc]', filename='x.py', encode=True) + + i, a = self.interp_errlog() + i.runsource(dedent(b'''\ + #encoding: utf-8 + + [u"%s", + abc]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=True) + self.assertEqual(self.err_lineno(a), 4) + + i, a = self.interp_errlog() + with self.assertRaises(ValueError): + i.runsource(dedent(b'''\ + #encoding: utf-8 + + [u"%s", + abc]''' % (u'åß∂ƒ'.encode('utf8'),)), + filename='x.py', + encode=True) + + @unittest.skipIf(py3, "encode only does anything in Python 2") + def test_runsource_bytestring_autoencode(self): + i, a = self.interp_errlog() + i.runsource(b'[1 + 1,\n abc]') + self.assertEqual(self.err_lineno(a), 2) + + i, a = self.interp_errlog() + i.runsource(b'[1 + 1,\nabc]', filename='x.py') + self.assertEqual(self.err_lineno(a), 2) + + i, a = self.interp_errlog() + i.runsource(dedent(b'''\ + #encoding: utf-8 + + [u"%s", + abc]''' % (u'åß∂ƒ'.encode('utf8'),))) + self.assertEqual(self.err_lineno(a), 4) + + i, a = self.interp_errlog() + i.runsource(dedent(b'''\ + #encoding: utf-8 + + [u"%s", + abc]''' % (u'åß∂ƒ'.encode('utf8'),))) + self.assertEqual(self.err_lineno(a), 4) From 0a4344eaace278922b259a95d7be2ef2b405f197 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 4 Aug 2016 13:06:53 -0400 Subject: [PATCH 0767/1650] fix runsource test for pypy Pypy has slightly different syntax error message for an encoding comment being present in a unicode string --- bpython/test/test_interpreter.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 7adf7297b..9878b588b 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -28,13 +28,10 @@ def interp_errlog(self): def err_lineno(self, a): strings = [x.__unicode__() for x in a] - print('looking for lineno') for line in reversed(strings): clean_line = remove_ansi(line) - print(clean_line) m = re.search(r'line (\d+)[,]', clean_line) if m: - print('found!', m.group(1)) return int(m.group(1)) return None @@ -150,8 +147,9 @@ def test_runsource_unicode_autoencode_and_noencode(self): i, a = self.interp_errlog() i.runsource(u'#encoding: utf-8\nabc', filename='x.py', encode=encode) - self.assertIn('SyntaxError: encoding', - ''.join(''.join(remove_ansi(x.__unicode__()) for x in a))) + self.assertIn('SyntaxError:', + ''.join(''.join(remove_ansi(x.__unicode__()) + for x in a))) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_unicode_encode(self): From f5ba95650c875e916f144aff689cc7e30917b367 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 5 Aug 2016 18:11:58 -0400 Subject: [PATCH 0768/1650] test existing suggestion box behavior --- bpython/curtsiesfrontend/repl.py | 3 +- bpython/test/test_curtsies_painting.py | 163 ++++++++++++++++++++++--- 2 files changed, 151 insertions(+), 15 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 98aa59d24..0bf0342bf 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1260,7 +1260,7 @@ def paint(self, about_to_exit=False, user_quit=False): the idea is that we don't need to worry about that here, instead every frame is completely redrawn because less state is cool! """ - # The hairiest function in the curtsies - a cleanup would be great. + # The hairiest function in the curtsies if about_to_exit: # exception to not changing state! self.clean_up_current_line_for_exit() @@ -1501,6 +1501,7 @@ def _set_current_line(self, line, update_completion=True, if clear_special_mode: self.special_mode = None self.unhighlight_paren() + current_line = property(_get_current_line, _set_current_line, None, "The current line") diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 87af5be62..a70daee6a 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -1,7 +1,9 @@ # coding: utf8 from __future__ import unicode_literals -import sys +import itertools +import string import os +import sys from contextlib import contextmanager from curtsies.formatstringarray import FormatStringTest, fsarray @@ -21,6 +23,7 @@ def setup_config(): config_struct = config.Struct() config.loadini(config_struct, os.devnull) + config_struct.cli_suggestion_width = 1 return config_struct @@ -48,13 +51,18 @@ def _request_refresh(inner_self): self.repl.rl_history = History() self.repl.height, self.repl.width = (5, 10) + @property + def locals(self): + return self.repl.coderunner.interp.locals + def assert_paint(self, screen, cursor_row_col): array, cursor_pos = self.repl.paint() self.assertFSArraysEqual(array, screen) self.assertEqual(cursor_pos, cursor_row_col) - def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None): - array, cursor_pos = self.repl.paint() + def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None, + **paint_kwargs): + array, cursor_pos = self.repl.paint(**paint_kwargs) self.assertFSArraysEqualIgnoringFormatting(array, screen) if cursor_row_col is not None: self.assertEqual(cursor_pos, cursor_row_col) @@ -96,15 +104,15 @@ def test_completion(self): self.cursor_offset = 2 if config.supports_box_chars(): screen = ['>>> an', - '┌───────────────────────┐', - '│ and any( │', - '└───────────────────────┘', + '┌──────────────────────────────┐', + '│ and any( │', + '└──────────────────────────────┘', 'Welcome to bpython! Press f'] else: screen = ['>>> an', - '+-----------------------+', - '| and any( |', - '+-----------------------+', + '+------------------------------+', + '| and any( |', + '+------------------------------+', 'Welcome to bpython! Press f'] self.assert_paint_ignoring_formatting(screen, (0, 4)) @@ -155,7 +163,7 @@ def output_to_repl(repl): sys.stdout, sys.stderr = old_out, old_err -class TestCurtsiesRewindRedraw(CurtsiesPaintingTest): +class HigherLevelCurtsiesPaintingTest(CurtsiesPaintingTest): def refresh(self): self.refresh_requests.append(RefreshRequestEvent()) @@ -194,6 +202,12 @@ def _request_refresh(inner_self): self.repl.rl_history = History() self.repl.height, self.repl.width = (5, 32) + def send_key(self, key): + self.repl.process_event('' if key == ' ' else key) + self.repl.paint() # has some side effects we need to be wary of + + +class TestCurtsiesRewindRedraw(HigherLevelCurtsiesPaintingTest): def test_rewind(self): self.repl.current_line = '1 + 1' self.enter() @@ -564,10 +578,6 @@ def test_unhighlight_paren_bugs(self): green("... ") + yellow(')') + bold(cyan(" "))]) self.assert_paint(screen, (1, 6)) - def send_key(self, key): - self.repl.process_event('' if key == ' ' else key) - self.repl.paint() # has some side effects we need to be wary of - def test_472(self): [self.send_key(c) for c in "(1, 2, 3)"] with output_to_repl(self.repl): @@ -586,3 +596,128 @@ def test_472(self): '(1, 4, 3)', '>>> '] self.assert_paint_ignoring_formatting(screen, (4, 4)) + + +def completion_target(num_names, chars_in_first_name=1): + class Class(object): + pass + + if chars_in_first_name < 1: + raise ValueError('need at least one char in each name') + elif chars_in_first_name == 1 and num_names > len(string.ascii_letters): + raise ValueError('need more chars to make so many names') + + names = gen_names() + if num_names > 0: + setattr(Class, 'a' * chars_in_first_name, 1) + next(names) # use the above instead of first name + for _, name in zip(range(num_names - 1), names): + setattr(Class, name, 0) + + return Class() + + +def gen_names(): + for letters in itertools.chain( + itertools.combinations_with_replacement(string.ascii_letters, 1), + itertools.combinations_with_replacement(string.ascii_letters, 2)): + yield ''.join(letters) + + +class TestCompletionHelpers(TestCase): + def test_gen_names(self): + self.assertEqual(list(zip([1, 2, 3], gen_names())), + [(1, 'a'), (2, 'b'), (3, 'c')]) + + def test_completion_target(self): + target = completion_target(14) + self.assertEqual(len([x for x in dir(target) + if not x.startswith('_')]), + 14) + + +class TestCurtsiesInfoboxPaint(HigherLevelCurtsiesPaintingTest): + def test_simple(self): + self.repl.width, self.repl.height = (20, 30) + self.locals['abc'] = completion_target(3, 50) + self.repl.current_line = 'abc' + self.repl.cursor_offset = 3 + self.repl.process_event('.') + screen = ['>>> abc.', + '+------------------+', + '| aaaaaaaaaaaaaaaa |', + '| b |', + '| c |', + '+------------------+'] + self.assert_paint_ignoring_formatting(screen, (0, 8)) + + def test_fill_screen(self): + self.repl.width, self.repl.height = (20, 15) + self.locals['abc'] = completion_target(20, 100) + self.repl.current_line = 'abc' + self.repl.cursor_offset = 3 + self.repl.process_event('.') + screen = ['>>> abc.', + '+------------------+', + '| aaaaaaaaaaaaaaaa |', + '| b |', + '| c |', + '| d |', + '| e |', + '| f |', + '| g |', + '| h |', + '| i |', + '| j |', + '| k |', + '| l |', + '+------------------+'] + self.assert_paint_ignoring_formatting(screen, (0, 8)) + + def test_lower_on_screen(self): + self.repl.get_top_usable_line = lambda: 10 # halfway down terminal + self.repl.width, self.repl.height = (20, 15) + self.locals['abc'] = completion_target(20, 100) + self.repl.current_line = 'abc' + self.repl.cursor_offset = 3 + self.repl.process_event('.') + screen = ['>>> abc.', + '+------------------+', + '| aaaaaaaaaaaaaaaa |', + '| b |', + '| c |', + '| d |', + '| e |', + '| f |', + '| g |', + '| h |', + '| i |', + '| j |', + '| k |', + '| l |', + '+------------------+'] + self.assert_paint_ignoring_formatting(screen, (0, 8)) + + def test_at_bottom_of_screen(self): + self.repl.get_top_usable_line = lambda: 17 # two lines from bottom + self.repl.width, self.repl.height = (20, 15) + self.locals['abc'] = completion_target(20, 100) + self.repl.current_line = 'abc' + self.repl.cursor_offset = 3 + self.repl.process_event('.') + screen = ['>>> abc.', + '+------------------+', + '| aaaaaaaaaaaaaaaa |', + '| b |', + '| c |', + '| d |', + '| e |', + '| f |', + '| g |', + '| h |', + '| i |', + '| j |', + '| k |', + '| l |', + '+------------------+'] + self.assert_paint_ignoring_formatting(screen, (0, 8)) From 9a371d7d19bab1d21ce23e8b4c8275aec791bee7 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 6 Jun 2016 20:09:59 -0400 Subject: [PATCH 0769/1650] choose completion box size based on screen size --- bpython/curtsiesfrontend/repl.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 0bf0342bf..e99f13edf 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1410,10 +1410,21 @@ def move_screen_up(current_line_start_row): if self.config.curtsies_list_above: info_max_rows = max(visible_space_above, visible_space_below) else: + # Logic for determining size of completion box # smallest allowed over-full completion box - minimum_possible_height = 20 + minimum_possible_height = 4 + # smallest amount of history that must be visible + try_preserve_history_height = 40 + preferred_height = max( + # always make infobox at least this height + minimum_possible_height, + + # there's so much space that we can preserve + # this much history and still expand the infobox + min_height - try_preserve_history_height) + info_max_rows = min(max(visible_space_below, - minimum_possible_height), + preferred_height), min_height - current_line_height - 1) infobox = paint.paint_infobox( info_max_rows, From 66c5771711b8b4707b820ed513df293d08b679cc Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 4 Aug 2016 15:38:42 -0400 Subject: [PATCH 0770/1650] move new variables to parameters --- bpython/curtsiesfrontend/repl.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index e99f13edf..e8f57551d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1251,7 +1251,9 @@ def current_output_line(self, value): self.current_stdouterr_line = '' self.stdin.current_line = '\n' - def paint(self, about_to_exit=False, user_quit=False): + def paint(self, about_to_exit=False, user_quit=False, + try_preserve_history_height=40, + min_infobox_height=4): """Returns an array of min_height or more rows and width columns, plus cursor position @@ -1259,6 +1261,10 @@ def paint(self, about_to_exit=False, user_quit=False): a diff and only write to the screen in portions that have changed, but the idea is that we don't need to worry about that here, instead every frame is completely redrawn because less state is cool! + + try_preserve_history_height is the the number of rows of content that + must be visible before the suggestion box scrolls the terminal in order + to display more than min_infobox_height rows of suggestions, docs etc. """ # The hairiest function in the curtsies if about_to_exit: @@ -1412,15 +1418,12 @@ def move_screen_up(current_line_start_row): else: # Logic for determining size of completion box # smallest allowed over-full completion box - minimum_possible_height = 4 - # smallest amount of history that must be visible - try_preserve_history_height = 40 preferred_height = max( # always make infobox at least this height - minimum_possible_height, + min_infobox_height, - # there's so much space that we can preserve - # this much history and still expand the infobox + # use this value if there's so much space that we can + # preserve this try_preserve_history_height rows history min_height - try_preserve_history_height) info_max_rows = min(max(visible_space_below, From aa4ff4273736fed0a6bbbc3ec9414032be7ecc42 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 5 Aug 2016 18:46:21 -0400 Subject: [PATCH 0771/1650] Test suggestion box behavior before/after #466. --- bpython/curtsiesfrontend/repl.py | 4 ++-- bpython/test/test_curtsies_painting.py | 28 ++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index e8f57551d..f8739d778 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1252,8 +1252,8 @@ def current_output_line(self, value): self.stdin.current_line = '\n' def paint(self, about_to_exit=False, user_quit=False, - try_preserve_history_height=40, - min_infobox_height=4): + try_preserve_history_height=30, + min_infobox_height=5): """Returns an array of min_height or more rows and width columns, plus cursor position diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index a70daee6a..2a324e591 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -696,7 +696,19 @@ def test_lower_on_screen(self): '| k |', '| l |', '+------------------+'] - self.assert_paint_ignoring_formatting(screen, (0, 8)) + # behavior before issue #466 + self.assert_paint_ignoring_formatting( + screen, try_preserve_history_height=0) + self.assert_paint_ignoring_formatting( + screen, min_infobox_height=100) + # behavior after issue #466 + screen = ['>>> abc.', + '+------------------+', + '| aaaaaaaaaaaaaaaa |', + '| b |', + '| c |', + '+------------------+'] + self.assert_paint_ignoring_formatting(screen) def test_at_bottom_of_screen(self): self.repl.get_top_usable_line = lambda: 17 # two lines from bottom @@ -720,4 +732,16 @@ def test_at_bottom_of_screen(self): '| k |', '| l |', '+------------------+'] - self.assert_paint_ignoring_formatting(screen, (0, 8)) + # behavior before issue #466 + self.assert_paint_ignoring_formatting( + screen, try_preserve_history_height=0) + self.assert_paint_ignoring_formatting( + screen, min_infobox_height=100) + # behavior after issue #466 + screen = ['>>> abc.', + '+------------------+', + '| aaaaaaaaaaaaaaaa |', + '| b |', + '| c |', + '+------------------+'] + self.assert_paint_ignoring_formatting(screen) From 9a88e73d3ce82029c51c67af5be011b78bdea469 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Mon, 8 Aug 2016 14:49:20 -0400 Subject: [PATCH 0772/1650] combine res --- bpython/inspection.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index b4d9bf4e0..8ebb9496c 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -283,14 +283,13 @@ def is_callable(obj): return callable(obj) -get_encoding_re = LazyReCompile(r'coding[:=]\s*([-\w.]+)') -get_encoding_line_re = LazyReCompile(r'^.*coding[:=]\s*[-\w.]+.*$') +get_encoding_line_re = LazyReCompile(r'^.*coding[:=]\s*([-\w.]+).*$') def get_encoding(obj): """Try to obtain encoding information of the source of an object.""" for line in inspect.findsource(obj)[0][:2]: - m = get_encoding_re.search(line) + m = get_encoding_line_re.search(line) if m: return m.group(1) return 'ascii' @@ -310,7 +309,7 @@ def get_encoding_file(fname): with io.open(fname, 'rt', encoding='ascii', errors='ignore') as f: for unused in range(2): line = f.readline() - match = get_encoding_re.search(line) + match = get_encoding_line_re.search(line) if match: return match.group(1) return 'ascii' From 8340aa232301c6ee91c317b4967aa253953bcba3 Mon Sep 17 00:00:00 2001 From: Simeon Visser Date: Sun, 14 Aug 2016 22:44:40 +0100 Subject: [PATCH 0773/1650] spelling: Remember --- doc/sphinx/source/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 01cdca357..e62b6b51c 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -64,7 +64,7 @@ Next install the install your development copy of bpython and its dependencies: $ sudo apt-get install python-watchdog python-urwid $ sudo apt-get install python-sphinx python-mock python-nose - Rememeber to replace ``python`` with ``python3`` in every package name if + Remember to replace ``python`` with ``python3`` in every package name if you intend to develop with Python 3. You also need to run `virtualenv` with `--system-site-packages` packages, if you want to use the packages provided by your distribution. From fa3dbbf7c174b308f0483e94e8b0152accda14c1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 27 Aug 2016 11:44:07 +0200 Subject: [PATCH 0774/1650] Ignore locking errors if locks are not available If $HOME is on a NFS, locking will fail with ENOLCK. Signed-off-by: Sebastian Ramacher --- bpython/filelock.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bpython/filelock.py b/bpython/filelock.py index a2d14cbe9..9c98b2acd 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -25,6 +25,7 @@ try: import fcntl + import errno has_fcntl = True except ImportError: has_fcntl = False @@ -42,14 +43,20 @@ def __init__(self, fd, mode=None): self.fd = fd self.mode = mode + self.locked = False def __enter__(self): if has_fcntl: - fcntl.flock(self.fd, self.mode) + try: + fcntl.flock(self.fd, self.mode) + self.locked = True + except IOError as e: + if e.errno != errno.ENOLCK: + raise e return self def __exit__(self, *args): - if has_fcntl: + if has_fcntl and self.locked: fcntl.flock(self.fd, fcntl.LOCK_UN) # vim: sw=4 ts=4 sts=4 ai et From dc243e5ef4c6a32f3a466314ad48a214bde0d041 Mon Sep 17 00:00:00 2001 From: Elias Dorneles Date: Thu, 29 Sep 2016 08:53:28 -0300 Subject: [PATCH 0775/1650] update tests expectations --- bpython/test/test_interpreter.py | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 9878b588b..95feb8b51 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -62,20 +62,20 @@ def test_traceback(self): def f(): return 1 / 0 - def g(): + def gfunc(): return f() - i.runsource('g()') + i.runsource('gfunc()') if pypy: - global_not_found = "global name 'g' is not defined" + global_not_found = "global name 'gfunc' is not defined" else: - global_not_found = "name 'g' is not defined" + global_not_found = "name 'gfunc' is not defined" expected = ( 'Traceback (most recent call last):\n File ' + green('""') + ', line ' + - bold(magenta('1')) + ', in ' + cyan('') + '\n g()\n' + + bold(magenta('1')) + ', in ' + cyan('') + '\n gfunc()\n' + bold(red('NameError')) + ': ' + cyan(global_not_found) + '\n') self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) @@ -133,19 +133,19 @@ def test_runsource_unicode_autoencode_and_noencode(self): # for auto and False, run the same tests for encode in ['auto', False]: i, a = self.interp_errlog() - i.runsource(u'[1 + 1,\nabc]', encode=encode) + i.runsource(u'[1 + 1,\nabcd]', encode=encode) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(u'[1 + 1,\nabc]', encode=encode) + i.runsource(u'[1 + 1,\nabcd]', encode=encode) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(u'#encoding: utf-8\nabc', encode=encode) + i.runsource(u'#encoding: utf-8\nabcd', encode=encode) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(u'#encoding: utf-8\nabc', + i.runsource(u'#encoding: utf-8\nabcd', filename='x.py', encode=encode) self.assertIn('SyntaxError:', ''.join(''.join(remove_ansi(x.__unicode__()) @@ -164,11 +164,11 @@ def test_runsource_unicode_encode(self): @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_bytestring_noencode(self): i, a = self.interp_errlog() - i.runsource(b'[1 + 1,\nabc]', encode=False) + i.runsource(b'[1 + 1,\nabcd]', encode=False) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(b'[1 + 1,\nabc]', filename='x.py', encode=False) + i.runsource(b'[1 + 1,\nabcd]', filename='x.py', encode=False) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() @@ -176,7 +176,7 @@ def test_runsource_bytestring_noencode(self): #encoding: utf-8 ["%s", - abc]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=False) + abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=False) self.assertEqual(self.err_lineno(a), 4) i, a = self.interp_errlog() @@ -184,26 +184,26 @@ def test_runsource_bytestring_noencode(self): #encoding: utf-8 ["%s", - abc]''' % (u'åß∂ƒ'.encode('utf8'),)), + abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), filename='x.py', encode=False) self.assertEqual(self.err_lineno(a), 4) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_bytestring_encode(self): i, a = self.interp_errlog() - i.runsource(b'[1 + 1,\nabc]', encode=True) + i.runsource(b'[1 + 1,\nabcd]', encode=True) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() with self.assertRaises(ValueError): - i.runsource(b'[1 + 1,\nabc]', filename='x.py', encode=True) + i.runsource(b'[1 + 1,\nabcd]', filename='x.py', encode=True) i, a = self.interp_errlog() i.runsource(dedent(b'''\ #encoding: utf-8 [u"%s", - abc]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=True) + abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=True) self.assertEqual(self.err_lineno(a), 4) i, a = self.interp_errlog() @@ -212,18 +212,18 @@ def test_runsource_bytestring_encode(self): #encoding: utf-8 [u"%s", - abc]''' % (u'åß∂ƒ'.encode('utf8'),)), + abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), filename='x.py', encode=True) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_bytestring_autoencode(self): i, a = self.interp_errlog() - i.runsource(b'[1 + 1,\n abc]') + i.runsource(b'[1 + 1,\n abcd]') self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(b'[1 + 1,\nabc]', filename='x.py') + i.runsource(b'[1 + 1,\nabcd]', filename='x.py') self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() @@ -231,7 +231,7 @@ def test_runsource_bytestring_autoencode(self): #encoding: utf-8 [u"%s", - abc]''' % (u'åß∂ƒ'.encode('utf8'),))) + abcd]''' % (u'åß∂ƒ'.encode('utf8'),))) self.assertEqual(self.err_lineno(a), 4) i, a = self.interp_errlog() @@ -239,5 +239,5 @@ def test_runsource_bytestring_autoencode(self): #encoding: utf-8 [u"%s", - abc]''' % (u'åß∂ƒ'.encode('utf8'),))) + abcd]''' % (u'åß∂ƒ'.encode('utf8'),))) self.assertEqual(self.err_lineno(a), 4) From dd4a003446a83ca59f0540130207d9eb975e354d Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 10 Oct 2016 14:51:47 +0200 Subject: [PATCH 0776/1650] bpdb: fix TypeError with pdbpp's `__init__` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using bpdb together with pdb++ [1], using `debug …` will cause a TypeError, because pdbpp passes in a `Config` kwarg [2]. …/venv/lib/python3.5/site-packages/pdb.py:647: in do_debug return orig_do_debug(self, arg) /usr/lib64/python3.5/pdb.py:1090: in do_debug p = Pdb(self.completekey, self.stdin, self.stdout) …/venv/lib/python3.5/site-packages/pdb.py:636: in new_pdb_with_config return self.__class__(*args, **kwds) E TypeError: __init__() got an unexpected keyword argument 'Config' This patch fixes bpdb to accept and pass on any args/kwargs. For reference, this is the stacktrace when pdb++ gets instantiated: File "…/venv/lib/python3.5/site-packages/pytestbpdb/ptbpdb.py", line 50, in set_trace pdb.BPdb().set_trace(frame) File "…/venv/lib/python3.5/site-packages/bpdb/debugger.py", line 33, in __init__ pdb.Pdb.__init__(self) File "…/venv/lib/python3.5/site-packages/pdb.py", line 198, in __init__ print(traceback.print_stack()) 1: https://pypi.python.org/pypi/pdbpp/ 2: https://bitbucket.org/antocuni/pdb/src/cf937bbd910a8f7fe2b84af7cf5ee9dc96c2fe25/pdb.py?fileviewer=file-view-default#pdb.py-633 --- bpdb/debugger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpdb/debugger.py b/bpdb/debugger.py index 60b51b853..373389146 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -29,8 +29,8 @@ class BPdb(pdb.Pdb): """ PDB with BPython support. """ - def __init__(self): - pdb.Pdb.__init__(self) + def __init__(self, *args, **kwargs): + pdb.Pdb.__init__(self, *args, **kwargs) self.rcLines = [] self.prompt = '(BPdb) ' self.intro = 'Use "B" to enter bpython, Ctrl-d to exit it.' From 5fe9743e3cb6ead1d05278a9c7956d92df7d8355 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Oct 2016 17:17:31 +0200 Subject: [PATCH 0777/1650] Update changelog Signed-off-by: Sebastian Ramacher --- CHANGELOG | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index cdc61254a..7077f4ec2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,10 +5,18 @@ Changelog ---- New features: +None Fixes: - - +* Fix various spelling mistakes. + Thanks to Josh Soref and Simeon Visser. +* #601: Fix Python 2 issues on Windows. + Thanks to Aditya Gupta. +* #614: Fix issues when view source. + Thanks to Daniel Hahler. +* #625: Fix issues when runnings scripts with non-ASCII characters. +* #639: Fix compatbility issues with pdb++. + Thanks to Daniel Hahler. Support for Python 2.6 has been dropped. From 2e353bb39e99faf9025583e2af3598099cd3abaa Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Oct 2016 17:26:59 +0200 Subject: [PATCH 0778/1650] Update changelog Signed-off-by: Sebastian Ramacher --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 7077f4ec2..5a70f8765 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ None Fixes: * Fix various spelling mistakes. Thanks to Josh Soref and Simeon Visser. +* #466: Better handle height of completion boxes. * #601: Fix Python 2 issues on Windows. Thanks to Aditya Gupta. * #614: Fix issues when view source. From d4d5a6a4e663ddbc85742f58b2567d9c585c0c48 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Oct 2016 19:16:25 +0200 Subject: [PATCH 0779/1650] Move to features --- CHANGELOG | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5a70f8765..d286ebfd1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,12 +5,11 @@ Changelog ---- New features: -None +* #466: Improve handling of completion box height. Fixes: * Fix various spelling mistakes. Thanks to Josh Soref and Simeon Visser. -* #466: Better handle height of completion boxes. * #601: Fix Python 2 issues on Windows. Thanks to Aditya Gupta. * #614: Fix issues when view source. From 05c3f506d4137f7f73294c12fe82f3aa52f1f0cd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Oct 2016 19:20:39 +0200 Subject: [PATCH 0780/1650] Start development of 0.17 --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index d286ebfd1..f7385b257 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,13 @@ Changelog ========= +0.17 +---- + +New features: + +Fixes: + 0.16 ---- From 206657206cad994863d7e0058a1395c0a5eb51a0 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Mon, 10 Oct 2016 19:31:32 +0000 Subject: [PATCH 0781/1650] Copyright bump and proper attribution. --- bpython/args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/args.py b/bpython/args.py index 91c8fc7fe..db5569d25 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -91,7 +91,7 @@ def parse(args, extras=None, ignore_stdin=False): if options.version: print('bpython version', __version__, end=" ") print('on top of Python', sys.version.split()[0]) - print('(C) 2008-2015 Bob Farrell, Andreas Stuehrk et al. ' + print('(C) 2008-2016 Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al. ' 'See AUTHORS for detail.') raise SystemExit From 23ce07f7ecbe57ef918ac765b82235dda15e5a37 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Oct 2016 23:47:39 +0200 Subject: [PATCH 0782/1650] Revert to use '%s' again Should fix #642 Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 2 +- bpython/curtsiesfrontend/repl.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 4ca5bef85..f4334b702 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -974,7 +974,7 @@ def p_key(self, key): try: source = self.get_source_of_current_name() except repl.SourceNotFound as e: - self.statusbar.message(str(e)) + self.statusbar.message('%s' % (e, )) else: if config.highlight_show_source: source = format(PythonLexer().get_tokens(source), diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3987e4733..e79d1298f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1702,7 +1702,7 @@ def show_source(self): try: source = self.get_source_of_current_name() except SourceNotFound as e: - self.status_bar.message(str(e)) + self.status_bar.message('%s' % (e, )) else: if self.config.highlight_show_source: source = format(PythonLexer().get_tokens(source), From d30cd9b2bc85c991f2327aed95bd3a8aa5c1737b Mon Sep 17 00:00:00 2001 From: Jakob Bowyer Date: Wed, 26 Oct 2016 12:49:04 +0100 Subject: [PATCH 0783/1650] Fix paste helper. Paste helper returns a tuple of 1 element which causes it to fail File line 868, in do_pastebin paste_url, removal_url = self.paster.paste(s) ValueError: need more than 1 value to unpack --- bpython/paste.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/paste.py b/bpython/paste.py index efa383b5b..ee8fc7b0e 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -109,4 +109,5 @@ def paste(self, s): raise PasteFailed(_('Failed to recognize the helper ' 'program\'s output as an URL.')) - return paste_url, + return paste_url, None + From bab615d988e94386049d824ffd73535a20421f65 Mon Sep 17 00:00:00 2001 From: Jacob Date: Wed, 26 Oct 2016 12:54:28 +0100 Subject: [PATCH 0784/1650] Update paste.py --- bpython/paste.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/paste.py b/bpython/paste.py index ee8fc7b0e..035812dcd 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -110,4 +110,3 @@ def paste(self, s): 'program\'s output as an URL.')) return paste_url, None - From ab1cbecc902f58e1a4f6d3ad4ca4c6c6ba325ad4 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 17 Nov 2016 22:53:07 -0500 Subject: [PATCH 0785/1650] fix weird boto docstrings --- bpython/curtsiesfrontend/replpainter.py | 10 +++++-- bpython/test/test_curtsies_painting.py | 40 ++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 27cdb979e..9dcdcf20f 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -74,7 +74,7 @@ def matches_lines(rows, columns, matches, current, config, format): if m != current else highlight_color( m.ljust(max_match_width)) - for m in matches[i:i+words_wide]) + for m in matches[i:i + words_wide]) for i in range(0, len(matches), words_wide)] logger.debug('match: %r' % current) @@ -161,6 +161,12 @@ def formatted_argspec(funcprops, arg_pos, columns, config): def formatted_docstring(docstring, columns, config): + if isinstance(docstring, bytes): + docstring = docstring.decode('utf8') + elif isinstance(docstring, str if py3 else unicode): + pass + else: + return [] color = func_for_letter(config.color_scheme['comment']) return sum(([color(x) for x in (display_linize(line, columns) if line else fmtstr(''))] @@ -211,7 +217,7 @@ def paint_last_events(rows, columns, names, config): return fsarray([]) width = min(max(len(name) for name in names), columns - 2) output_lines = [] - output_lines.append(config.left_top_corner+config.top_border * width + + output_lines.append(config.left_top_corner + config.top_border * width + config.right_top_corner) for name in reversed(names[max(0, len(names) - (rows - 2)):]): output_lines.append(config.left_border + name[:width].center(width) + diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 2a324e591..ec0d102c2 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -1,8 +1,9 @@ # coding: utf8 from __future__ import unicode_literals import itertools -import string import os +import pydoc +import string import sys from contextlib import contextmanager @@ -136,6 +137,43 @@ def test_formatted_docstring(self): 'Also has side effects']) self.assertFSArraysEqualIgnoringFormatting(actual, expected) + def test_unicode_docstrings(self): + "A bit of a special case in Python 2" + # issue 653 + + def foo(): + u"åß∂ƒ" + + actual = replpainter.formatted_docstring( + foo.__doc__, 40, config=setup_config()) + expected = fsarray([u'åß∂ƒ']) + self.assertFSArraysEqualIgnoringFormatting(actual, expected) + + def test_nonsense_docstrings(self): + for docstring in [123, {}, [], ]: + try: + replpainter.formatted_docstring( + docstring, 40, config=setup_config()) + except Exception: + self.fail('bad docstring caused crash: {!r}'.format(docstring)) + + def test_weird_boto_docstrings(self): + # Boto does something like this. + # botocore: botocore/docs/docstring.py + class WeirdDocstring(str): + # a mighty hack. See botocore/docs/docstring.py + def expandtabs(self, tabsize=8): + return u'asdfåß∂ƒ'.expandtabs(tabsize) + + def foo(): + pass + + foo.__doc__ = WeirdDocstring() + wd = pydoc.getdoc(foo) + actual = replpainter.formatted_docstring(wd, 40, config=setup_config()) + expected = fsarray([u'asdfåß∂ƒ']) + self.assertFSArraysEqualIgnoringFormatting(actual, expected) + def test_paint_lasts_events(self): actual = replpainter.paint_last_events(4, 100, ['a', 'b', 'c'], config=setup_config()) From a40c8635a7022bb32beab1de42f058610e0a186f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 19 Nov 2016 11:19:39 +0100 Subject: [PATCH 0786/1650] Add a TODO for proper error handling --- bpython/curtsiesfrontend/replpainter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 9dcdcf20f..36c9c1324 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -166,6 +166,7 @@ def formatted_docstring(docstring, columns, config): elif isinstance(docstring, str if py3 else unicode): pass else: + # TODO: fail properly here and catch possible exceptions in callers. return [] color = func_for_letter(config.color_scheme['comment']) return sum(([color(x) for x in (display_linize(line, columns) if line else From 27a9b8825573493f9f1bbad706597b61f5d3af2d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Dec 2016 21:52:54 +0100 Subject: [PATCH 0787/1650] Simply require Sphinx >= 1.1.3 --- .travis.install.sh | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index b73e9e5d6..8522aa261 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -32,5 +32,5 @@ if [[ $RUN == nosetests ]]; then python setup.py install elif [[ $RUN == build_sphinx ]]; then # documentation specific dependencies - pip install sphinx + pip install 'sphinx >=1.1.3' fi diff --git a/setup.py b/setup.py index 94082134b..c282df7aa 100755 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ try: import sphinx from sphinx.setup_command import BuildDoc - if sphinx.__version__ == '1.1.2': + if sphinx.__version__ >= '1.1.3': # Sphinx 1.1.2 is buggy and building bpython with that version fails. # See #241. using_sphinx = False From 1d23529095a41c213f50d983043f47e7c8ef8e0e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Dec 2016 22:05:27 +0100 Subject: [PATCH 0788/1650] Fix typos --- bpython/curtsiesfrontend/repl.py | 2 +- bpython/formatter.py | 2 +- bpython/lazyre.py | 4 ++-- bpython/urwid.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index e79d1298f..8d7f9fb7e 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -403,7 +403,7 @@ def __init__(self, self.request_paint_to_pad_bottom = 0 - # offscreen command yields results different from scrollback bufffer + # offscreen command yields results different from scrollback buffer self.inconsistent_history = False # history error message has already been displayed diff --git a/bpython/formatter.py b/bpython/formatter.py index 6c0ab75ef..96ee6ed28 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -49,7 +49,7 @@ \x04 represents the end of the string; this is necessary because the strings are all joined together at the end so the parser needs them - as delimeters + as delimiters """ diff --git a/bpython/lazyre.py b/bpython/lazyre.py index e27b8de5f..54e8d565b 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -26,8 +26,8 @@ class LazyReCompile(object): """Compile regular expressions on first use - This class allows to store regular expressions and compiles them on first - use.""" + This class allows one to store regular expressions and compiles them on + first use.""" def __init__(self, regex, flags=0): self.regex = regex diff --git a/bpython/urwid.py b/bpython/urwid.py index d2c392d72..ceb8717b8 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -233,7 +233,7 @@ def _reset_timer(self): def prompt(self, s=None, single=False): """Prompt the user for some input (with the optional prompt 's'). After - the user hit enter the signal 'prompt_result' will be emited and the + the user hit enter the signal 'prompt_result' will be emitted and the status bar will be reset. If single is True, the first keypress will be returned.""" From f75870575127bd1be9afb5888279c4fcf9f0a275 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Dec 2016 22:14:03 +0100 Subject: [PATCH 0789/1650] Fix sphinx version check --- setup.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index c282df7aa..44495534c 100755 --- a/setup.py +++ b/setup.py @@ -24,12 +24,9 @@ try: import sphinx from sphinx.setup_command import BuildDoc - if sphinx.__version__ >= '1.1.3': - # Sphinx 1.1.2 is buggy and building bpython with that version fails. - # See #241. - using_sphinx = False - else: - using_sphinx = True + # Sphinx 1.1.2 is buggy and building bpython with that version fails. + # See #241. + using_sphinx = sphinx.__version__ >= '1.1.3' except ImportError: using_sphinx = False From 305773234fde003a00e115d6858de82dd93b76e9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Dec 2016 22:14:14 +0100 Subject: [PATCH 0790/1650] Use implementation agnostic names --- bpython/curtsies.py | 8 ++++---- bpython/curtsiesfrontend/coderunner.py | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 3f0f4be45..9f7f6d57d 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -12,7 +12,7 @@ import curtsies.events from bpython.curtsiesfrontend.repl import BaseRepl -from bpython.curtsiesfrontend.coderunner import SystemExitFromCodeGreenlet +from bpython.curtsiesfrontend.coderunner import SystemExitFromCodeRunner from bpython.curtsiesfrontend.interpreter import Interp from bpython import args as bpargs from bpython import translations @@ -87,11 +87,11 @@ def process_event_and_paint(self, e): try: if e is not None: self.process_event(e) - except (SystemExitFromCodeGreenlet, SystemExit) as err: + except (SystemExitFromCodeRunner, SystemExit) as err: array, cursor_pos = self.paint( about_to_exit=True, user_quit=isinstance(err, - SystemExitFromCodeGreenlet)) + SystemExitFromCodeRunner)) scrolled = self.window.render_to_terminal(array, cursor_pos) self.scroll_offset += scrolled raise @@ -188,7 +188,7 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): with repl: repl.height, repl.width = win.t.height, win.t.width exit_value = repl.mainloop() - except (SystemExitFromCodeGreenlet, SystemExit) as e: + except (SystemExitFromCodeRunner, SystemExit) as e: exit_value = e.args return extract_exit_value(exit_value) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 79fb215ff..630caf9a0 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -25,32 +25,32 @@ class SigintHappened(object): """If this class is returned, a SIGINT happened while the main greenlet""" -class SystemExitFromCodeGreenlet(SystemExit): +class SystemExitFromCodeRunner(SystemExit): """If this class is returned, a SystemExit happened while in the code greenlet""" -class RequestFromCodeGreenlet(object): - """Message from the code greenlet""" +class RequestFromCodeRunner(object): + """Message from the code runner""" -class Wait(RequestFromCodeGreenlet): +class Wait(RequestFromCodeRunner): """Running code would like the main loop to run for a bit""" -class Refresh(RequestFromCodeGreenlet): +class Refresh(RequestFromCodeRunner): """Running code would like the main loop to refresh the display""" -class Done(RequestFromCodeGreenlet): +class Done(RequestFromCodeRunner): """Running code is done running""" -class Unfinished(RequestFromCodeGreenlet): +class Unfinished(RequestFromCodeRunner): """Source code wasn't executed because it wasn't fully formed""" -class SystemExitRequest(RequestFromCodeGreenlet): +class SystemExitRequest(RequestFromCodeRunner): """Running code raised a SystemExit""" def __init__(self, args): @@ -145,7 +145,7 @@ def run_code(self, for_code=None): request = self.code_greenlet.switch(for_code) logger.debug('request received from code was %r', request) - if not isinstance(request, RequestFromCodeGreenlet): + if not isinstance(request, RequestFromCodeRunner): raise ValueError("Not a valid value from code greenlet: %r" % request) if isinstance(request, (Wait, Refresh)): @@ -160,7 +160,7 @@ def run_code(self, for_code=None): return request elif isinstance(request, SystemExitRequest): self._unload_code() - raise SystemExitFromCodeGreenlet(request.args) + raise SystemExitFromCodeRunner(request.args) def sigint_handler(self, *args): """SIGINT handler to use while code is running or request being From 2b8c30b94690941c00ae8d70a6fe11d5d43d8461 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Dec 2016 22:24:25 +0100 Subject: [PATCH 0791/1650] Rename {main,code,request}_greenlet to *_context --- bpython/curtsiesfrontend/coderunner.py | 42 ++++++++++++------------- bpython/curtsiesfrontend/interaction.py | 26 +++++++-------- bpython/curtsiesfrontend/repl.py | 4 +-- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 630caf9a0..fc1e9dac6 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -61,13 +61,13 @@ class CodeRunner(object): """Runs user code in an interpreter. Running code requests a refresh by calling - request_from_main_greenlet(force_refresh=True), which + request_from_main_context(force_refresh=True), which suspends execution of the code and switches back to the main greenlet After load_code() is called with the source code to be run, the run_code() method should be called to start running the code. The running code may request screen refreshes and user input - by calling request_from_main_greenlet. + by calling request_from_main_context. When this are called, the running source code cedes control, and the current run_code() method call returns. @@ -93,32 +93,32 @@ def __init__(self, interp=None, request_refresh=lambda: None): """ self.interp = interp or code.InteractiveInterpreter() self.source = None - self.main_greenlet = greenlet.getcurrent() - self.code_greenlet = None + self.main_context = greenlet.getcurrent() + self.code_context = None self.request_refresh = request_refresh # waiting for response from main thread self.code_is_waiting = False # sigint happened while in main thread - self.sigint_happened_in_main_greenlet = False + self.sigint_happened_in_main_context = False self.orig_sigint_handler = None @property def running(self): """Returns greenlet if code has been loaded greenlet has been started""" - return self.source and self.code_greenlet + return self.source and self.code_context def load_code(self, source): """Prep code to be run""" assert self.source is None, "you shouldn't load code when some is " \ "already running" self.source = source - self.code_greenlet = None + self.code_context = None def _unload_code(self): """Called when done running code""" self.source = None - self.code_greenlet = None + self.code_context = None self.code_is_waiting = False def run_code(self, for_code=None): @@ -128,21 +128,21 @@ def run_code(self, for_code=None): if source code is complete, returns "done" if source code is incomplete, returns "unfinished" """ - if self.code_greenlet is None: + if self.code_context is None: assert self.source is not None - self.code_greenlet = greenlet.greenlet(self._blocking_run_code) + self.code_context = greenlet.greenlet(self._blocking_run_code) self.orig_sigint_handler = signal.getsignal(signal.SIGINT) signal.signal(signal.SIGINT, self.sigint_handler) - request = self.code_greenlet.switch() + request = self.code_context.switch() else: assert self.code_is_waiting self.code_is_waiting = False signal.signal(signal.SIGINT, self.sigint_handler) - if self.sigint_happened_in_main_greenlet: - self.sigint_happened_in_main_greenlet = False - request = self.code_greenlet.switch(SigintHappened) + if self.sigint_happened_in_main_context: + self.sigint_happened_in_main_context = False + request = self.code_context.switch(SigintHappened) else: - request = self.code_greenlet.switch(for_code) + request = self.code_context.switch(for_code) logger.debug('request received from code was %r', request) if not isinstance(request, RequestFromCodeRunner): @@ -165,13 +165,13 @@ def run_code(self, for_code=None): def sigint_handler(self, *args): """SIGINT handler to use while code is running or request being fulfilled""" - if greenlet.getcurrent() is self.code_greenlet: + if greenlet.getcurrent() is self.code_context: logger.debug('sigint while running user code!') raise KeyboardInterrupt() else: logger.debug('sigint while fulfilling code request sigint handler ' 'running!') - self.sigint_happened_in_main_greenlet = True + self.sigint_happened_in_main_context = True def _blocking_run_code(self): try: @@ -180,15 +180,15 @@ def _blocking_run_code(self): return SystemExitRequest(e.args) return Unfinished() if unfinished else Done() - def request_from_main_greenlet(self, force_refresh=False): + def request_from_main_context(self, force_refresh=False): """Return the argument passed in to .run_code(for_code) Nothing means calls to run_code must be... ??? """ if force_refresh: - value = self.main_greenlet.switch(Refresh()) + value = self.main_context.switch(Refresh()) else: - value = self.main_greenlet.switch(Wait()) + value = self.main_context.switch(Wait()) if value is SigintHappened: raise KeyboardInterrupt() return value @@ -211,7 +211,7 @@ def write(self, s, *args, **kwargs): if not py3 and isinstance(s, str): s = s.decode(getpreferredencoding(), 'ignore') self.on_write(s, *args, **kwargs) - return self.coderunner.request_from_main_greenlet(force_refresh=True) + return self.coderunner.request_from_main_context(force_refresh=True) # Some applications which use curses require that sys.stdout # have a method called fileno. One example is pwntools. This diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 3022ba430..94ce6a3cf 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -40,8 +40,8 @@ def __init__(self, self.permanent_stack = [] if permanent_text: self.permanent_stack.append(permanent_text) - self.main_greenlet = greenlet.getcurrent() - self.request_greenlet = None + self.main_context = greenlet.getcurrent() + self.request_context = None self.request_refresh = request_refresh self.schedule_refresh = schedule_refresh @@ -76,13 +76,13 @@ def process_event(self, e): assert self.in_prompt or self.in_confirm or self.waiting_for_refresh if isinstance(e, RefreshRequestEvent): self.waiting_for_refresh = False - self.request_greenlet.switch() + self.request_context.switch() elif isinstance(e, events.PasteEvent): for ee in e.events: # strip control seq self.add_normal_character(ee if len(ee) == 1 else ee[-1]) elif e in [''] or isinstance(e, events.SigIntEvent): - self.request_greenlet.switch(False) + self.request_context.switch(False) self.escape() elif e in edit_keys: self.cursor_offset_in_line, self._current_line = edit_keys[e]( @@ -94,12 +94,12 @@ def process_event(self, e): elif self.in_prompt and e in ("\n", "\r", "", "Ctrl-m>"): line = self._current_line self.escape() - self.request_greenlet.switch(line) + self.request_context.switch(line) elif self.in_confirm: if e in ('y', 'Y'): - self.request_greenlet.switch(True) + self.request_context.switch(True) else: - self.request_greenlet.switch(False) + self.request_context.switch(False) self.escape() else: # add normal character self.add_normal_character(e) @@ -140,26 +140,26 @@ def should_show_message(self): # interaction interface - should be called from other greenlets def notify(self, msg, n=3, wait_for_keypress=False): - self.request_greenlet = greenlet.getcurrent() + self.request_context = greenlet.getcurrent() self.message_time = n self.message(msg, schedule_refresh=wait_for_keypress) self.waiting_for_refresh = True self.request_refresh() - self.main_greenlet.switch(msg) + self.main_context.switch(msg) # below Really ought to be called from greenlets other than main because # they block def confirm(self, q): """Expected to return True or False, given question prompt q""" - self.request_greenlet = greenlet.getcurrent() + self.request_context = greenlet.getcurrent() self.prompt = q self.in_confirm = True - return self.main_greenlet.switch(q) + return self.main_context.switch(q) def file_prompt(self, s): """Expected to return a file name, given """ - self.request_greenlet = greenlet.getcurrent() + self.request_context = greenlet.getcurrent() self.prompt = s self.in_prompt = True - result = self.main_greenlet.switch(s) + result = self.main_context.switch(s) return result diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 8d7f9fb7e..c45f0f2b7 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -128,7 +128,7 @@ def process_event(self, e): self.cursor_offset, self.current_line = self.rl_char_sequences[e]( self.cursor_offset, self.current_line) elif isinstance(e, events.SigIntEvent): - self.coderunner.sigint_happened_in_main_greenlet = True + self.coderunner.sigint_happened_in_main_context = True self.has_focus = False self.current_line = '' self.cursor_offset = 0 @@ -179,7 +179,7 @@ def add_input_character(self, e): def readline(self): self.has_focus = True self.repl.send_to_stdin(self.current_line) - value = self.coderunner.request_from_main_greenlet() + value = self.coderunner.request_from_main_context() self.readline_results.append(value) return value From f35f8056e1e7dff19774734b0ef06439fb2298bb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Dec 2016 23:01:05 +0100 Subject: [PATCH 0792/1650] Be consistent with history file expansion --- bpython/repl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index cd3dc9a5c..03c4debf7 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -899,8 +899,9 @@ def push(self, s, insert_into_history=True): return more def insert_into_history(self, s): + pythonhist = os.path.expanduser(self.config.hist_file) try: - self.rl_history.append_reload_and_write(s, self.config.hist_file, + self.rl_history.append_reload_and_write(s, pythonhist, getpreferredencoding()) except RuntimeError as e: self.interact.notify(u"%s" % (e, )) From 205ca1be204c12ebac162b9fe95945b290f15a19 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Dec 2016 23:05:12 +0100 Subject: [PATCH 0793/1650] Disable history for tests --- bpython/test/test.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/test/test.config b/bpython/test/test.config index 334706bab..b7397d44f 100644 --- a/bpython/test/test.config +++ b/bpython/test/test.config @@ -1,3 +1,3 @@ [general] -hist_file = /dev/null -paste_time = 0 \ No newline at end of file +hist_size = 0 +paste_time = 0 From 5d1c976585123710ea2e438655d24603fa5565ca Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Dec 2016 22:55:29 +0100 Subject: [PATCH 0794/1650] Move TEST_CONFIG to bpython.test --- bpython/test/__init__.py | 4 ++++ bpython/test/test_crashers.py | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index 7aa5d1a9d..39b5f5cb3 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -13,6 +13,7 @@ from bpython.translations import init from bpython._py3compat import py3 from six.moves import builtins +import os class FixLanguageTestCase(unittest.TestCase): @@ -33,3 +34,6 @@ class MagicIterMock(mock.MagicMock): def builtin_target(obj): """Returns mock target string of a builtin""" return '%s.%s' % (builtins.__name__, obj.__name__) + + +TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index e2f1ccabb..341f6b45f 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -6,7 +6,7 @@ import termios import textwrap -from bpython.test import unittest +from bpython.test import unittest, TEST_CONFIG try: from twisted.internet import reactor @@ -32,8 +32,6 @@ def identity(func): return func return identity -TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") - def set_win_size(fd, rows, columns): s = struct.pack('HHHH', rows, columns, 0, 0) From a6fef36886fa8c02f0eaadbfb3a3979bea327e6a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Dec 2016 23:13:33 +0100 Subject: [PATCH 0795/1650] Use test config (fixes #654) --- bpython/test/test_curtsies_painting.py | 4 ++-- bpython/test/test_curtsies_repl.py | 4 ++-- bpython/test/test_repl.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index ec0d102c2..80f29ab78 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -18,12 +18,12 @@ from bpython.repl import History from bpython.curtsiesfrontend.repl import INCONSISTENT_HISTORY_MSG, \ CONTIGUITY_BROKEN_MSG -from bpython.test import FixLanguageTestCase as TestCase +from bpython.test import FixLanguageTestCase as TestCase, TEST_CONFIG def setup_config(): config_struct = config.Struct() - config.loadini(config_struct, os.devnull) + config.loadini(config_struct, TEST_CONFIG) config_struct.cli_suggestion_width = 1 return config_struct diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 03a7b8ae3..3cf25da85 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -18,7 +18,7 @@ from bpython import args from bpython._py3compat import py3 from bpython.test import (FixLanguageTestCase as TestCase, MagicIterMock, mock, - unittest) + unittest, TEST_CONFIG) from curtsies import events @@ -31,7 +31,7 @@ def invalidate_caches(): def setup_config(conf): config_struct = config.Struct() - config.loadini(config_struct, os.devnull) + config.loadini(config_struct, TEST_CONFIG) for key, value in conf.items(): if not hasattr(config_struct, key): raise ValueError("%r is not a valid config attribute" % (key, )) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index a27de674d..34d7f774a 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -11,7 +11,7 @@ from bpython._py3compat import py3 from bpython import config, repl, cli, autocomplete from bpython.test import MagicIterMock, mock, FixLanguageTestCase as TestCase -from bpython.test import unittest +from bpython.test import unittest, TEST_CONFIG pypy = 'PyPy' in sys.version @@ -19,7 +19,7 @@ def setup_config(conf): config_struct = config.Struct() - config.loadini(config_struct, os.devnull) + config.loadini(config_struct, TEST_CONFIG) if 'autocomplete_mode' in conf: config_struct.autocomplete_mode = conf['autocomplete_mode'] return config_struct From 3e99cc5f09da031d35467529fa0958722f819e51 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Dec 2016 00:12:40 +0100 Subject: [PATCH 0796/1650] Port FileLock to Windows Signed-off-by: Sebastian Ramacher --- bpython/filelock.py | 89 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/bpython/filelock.py b/bpython/filelock.py index 9c98b2acd..958e8220d 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -2,7 +2,7 @@ # The MIT License # -# Copyright (c) 2015 Sebastian Ramacher +# Copyright (c) 2015-2016 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -30,33 +30,86 @@ except ImportError: has_fcntl = False +try: + import msvcrt + has_msvcrt = True +except ImportError: + has_msvcrt = False + + +class BaseLock(object): + """Base class for file locking + """ + + def __init__(self, fd): + self.fd = fd + self.locked = False -class FileLock(object): - """Simple file locking + def acquire(self): + pass - On platforms without fcntl, all operations in this class are no-ops. + def release(self): + pass + + def __enter__(self): + self.acquire() + + def __exit__(self, *args): + if self.locked: + self.release() + + def __del__(self): + if self.locked: + self.release() + + +class UnixFileLock(BaseLock): + """Simple file locking for Unix using fcntl """ def __init__(self, fd, mode=None): - if has_fcntl and mode is None: - mode = fcntl.LOCK_EX + super(UnixFileLock, self).__init__(fd) - self.fd = fd + if mode is None: + mode = fcntl.LOCK_EX self.mode = mode + + def acquire(self): + try: + fcntl.flock(self.fd, self.mode) + self.locked = True + except IOError as e: + if e.errno != errno.ENOLCK: + raise e + return self + + def release(self): + fcntl.flock(self.fd, fcntl.LOCK_UN) self.locked = False - def __enter__(self): - if has_fcntl: - try: - fcntl.flock(self.fd, self.mode) - self.locked = True - except IOError as e: - if e.errno != errno.ENOLCK: - raise e + +class WindowsFileLock(BaseLock): + """Simple file locking for Windows using msvcrt + """ + + def __init__(self, fd, mode=None): + super(WindowsFileLock, self).__init__(fd) + + def acquire(self): + msvcrt.locking(self.fd, msvcrt.LK_NBLCK, 1) + self.locked = True return self - def __exit__(self, *args): - if has_fcntl and self.locked: - fcntl.flock(self.fd, fcntl.LOCK_UN) + def release(self): + msvcrt.locking(self.fd, msvcrt.LK_UNLCK, 1) + self.locked = False + + +if has_fcntl: + FileLock = UnixFileLock +elif has_msvcrt: + FileLock = WindowsFileLock +else: + FileLock = BaseLock # vim: sw=4 ts=4 sts=4 ai et From 4e611563e7bd2e4aee37b8f2afbc5c66ffe7ee9b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Dec 2016 00:17:33 +0100 Subject: [PATCH 0797/1650] Fix __enter__ Signed-off-by: Sebastian Ramacher --- bpython/filelock.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/filelock.py b/bpython/filelock.py index 958e8220d..724cb4498 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -53,6 +53,7 @@ def release(self): def __enter__(self): self.acquire() + return self def __exit__(self, *args): if self.locked: @@ -81,7 +82,6 @@ def acquire(self): except IOError as e: if e.errno != errno.ENOLCK: raise e - return self def release(self): fcntl.flock(self.fd, fcntl.LOCK_UN) @@ -98,7 +98,6 @@ def __init__(self, fd, mode=None): def acquire(self): msvcrt.locking(self.fd, msvcrt.LK_NBLCK, 1) self.locked = True - return self def release(self): msvcrt.locking(self.fd, msvcrt.LK_UNLCK, 1) From 8df76efd1f6258b743c32e1e4e82e76e4e1c2ad5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Dec 2016 00:50:13 +0100 Subject: [PATCH 0798/1650] Fix name of config variable --- bpython/test/test.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test.config b/bpython/test/test.config index b7397d44f..21f55c121 100644 --- a/bpython/test/test.config +++ b/bpython/test/test.config @@ -1,3 +1,3 @@ [general] -hist_size = 0 +hist_length = 0 paste_time = 0 From b51df1cf369a49990282326de7067d7f4228fc5c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Dec 2016 01:15:58 +0100 Subject: [PATCH 0799/1650] Remove History workaround We already have a clean history. --- bpython/test/test_curtsies_painting.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 80f29ab78..3f7414b9e 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -15,7 +15,6 @@ from bpython import config, inspection from bpython.curtsiesfrontend.repl import BaseRepl from bpython.curtsiesfrontend import replpainter -from bpython.repl import History from bpython.curtsiesfrontend.repl import INCONSISTENT_HISTORY_MSG, \ CONTIGUITY_BROKEN_MSG from bpython.test import FixLanguageTestCase as TestCase, TEST_CONFIG @@ -48,8 +47,6 @@ class TestRepl(BaseRepl): def _request_refresh(inner_self): pass self.repl = TestRepl(config=setup_config()) - # clear history - self.repl.rl_history = History() self.repl.height, self.repl.width = (5, 10) @property @@ -236,8 +233,6 @@ class TestRepl(BaseRepl): def _request_refresh(inner_self): self.refresh() self.repl = TestRepl(banner='', config=setup_config()) - # clear history - self.repl.rl_history = History() self.repl.height, self.repl.width = (5, 32) def send_key(self, key): From ee6afe1728f71e21a7d3c8bf46d72f0320471329 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Dec 2016 01:33:37 +0100 Subject: [PATCH 0800/1650] entries must always have at least one element Signed-off-by: Sebastian Ramacher --- bpython/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/history.py b/bpython/history.py index 4e63bf776..8093aca7a 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -185,7 +185,7 @@ def load_from(self, fd): entries = [] for line in fd: self.append_to(entries, line) - return entries + return entries if len(entries) else [''] def save(self, filename, encoding, lines=0): fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.TRUNC, From 27322d88d837c41db5ac443a04722ed928d32cd4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Dec 2016 01:33:44 +0100 Subject: [PATCH 0801/1650] Also specify hist_file (really fixes #654) --- bpython/test/test.config | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/test/test.config b/bpython/test/test.config index 21f55c121..38a233624 100644 --- a/bpython/test/test.config +++ b/bpython/test/test.config @@ -1,3 +1,4 @@ [general] hist_length = 0 +hist_file = /dev/null paste_time = 0 From 36457741e9d5fcdef518a5fd76a6a691387a5b88 Mon Sep 17 00:00:00 2001 From: Atri Bhattacharya Date: Sun, 25 Dec 2016 21:54:14 +0530 Subject: [PATCH 0802/1650] Close

      node in description correctly. This trivial markup fix ensures that the

      node is closed in the right place, so that when software centers like gnome-software show the appdata, the description section does not get curtailed at the place where the

        begins. --- data/bpython.appdata.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/data/bpython.appdata.xml b/data/bpython.appdata.xml index 721e56e98..8bcf0e163 100644 --- a/data/bpython.appdata.xml +++ b/data/bpython.appdata.xml @@ -11,16 +11,16 @@

        bpython is a fancy interface to the Python interpreter. It has the following features: -

          -
        • In-line syntax highlighting.
        • -
        • Readline-like autocomplete with suggestion displayed as you type.
        • -
        • Expected parameter list for any Python function.
        • -
        • "Rewind" function to pop the last line of code from memory and re-evaluate.
        • -
        • Send the code you've entered off to a pastebin.
        • -
        • Save the code you've entered to a file.
        • -
        • Auto-indentation.
        • -

        +
          +
        • In-line syntax highlighting.
        • +
        • Readline-like autocomplete with suggestion displayed as you type.
        • +
        • Expected parameter list for any Python function.
        • +
        • "Rewind" function to pop the last line of code from memory and re-evaluate.
        • +
        • Send the code you've entered off to a pastebin.
        • +
        • Save the code you've entered to a file.
        • +
        • Auto-indentation.
        • +
        http://www.bpython-interpreter.org/ https://github.com/bpython/bpython/issues From 237a493b2c99cefe6c28ad752cdbe633781a732d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 28 Dec 2016 15:06:41 +0100 Subject: [PATCH 0803/1650] Test with Python 3.6 To be replaced with 3.6 once available. Signed-off-by: Sebastian Ramacher --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6383194d8..3d761f016 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ python: - "3.3" - "3.4" - "3.5" + - "3.6-dev" - "pypy" - "pypy3" From 2480377a5ffcc0aca24c49ccf07ec5c44f26ed16 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Jan 2017 21:10:51 +0100 Subject: [PATCH 0804/1650] Remove unused member variable Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/interpreter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 410a9f3fa..d157b71b4 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -79,7 +79,6 @@ def __init__(self, locals=None, encoding=None): encoding = getpreferredencoding() ReplInterpreter.__init__(self, locals, encoding) - self.locals = locals self.compile = CommandCompiler() # typically changed after being instantiated From cb6eb39dbbf53efa14169c3cfca0364fc8c812ed Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Jan 2017 21:12:04 +0100 Subject: [PATCH 0805/1650] Default locals is set-up in super().__init__ Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/interpreter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index d157b71b4..9da08744d 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -73,8 +73,6 @@ def __init__(self, locals=None, encoding=None): We include an argument for the outfile to pass to the formatter for it to write to. """ - if locals is None: - locals = {"__name__": "__console__", "__doc__": None} if encoding is None: encoding = getpreferredencoding() ReplInterpreter.__init__(self, locals, encoding) From 4a36757b10f7a3b304c37a5eb3b7e50505a7c383 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Jan 2017 21:17:04 +0100 Subject: [PATCH 0806/1650] Remove code duplication Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/interpreter.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 9da08744d..22da121a1 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -73,8 +73,6 @@ def __init__(self, locals=None, encoding=None): We include an argument for the outfile to pass to the formatter for it to write to. """ - if encoding is None: - encoding = getpreferredencoding() ReplInterpreter.__init__(self, locals, encoding) self.compile = CommandCompiler() From 70477ef89e7857c869a374f04642115eceb8fd28 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Jan 2017 21:32:32 +0100 Subject: [PATCH 0807/1650] Remove more duplicated code --- bpython/curtsiesfrontend/interpreter.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 22da121a1..223fe9618 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,5 +1,4 @@ import sys -from codeop import CommandCompiler from six import iteritems, text_type from pygments.token import Generic, Token, Keyword, Name, Comment, String @@ -75,8 +74,6 @@ def __init__(self, locals=None, encoding=None): """ ReplInterpreter.__init__(self, locals, encoding) - self.compile = CommandCompiler() - # typically changed after being instantiated # but used when interpreter used corresponding REPL def write(err_line): From f189da1789211d267ee8d9ac345dcb3fa90cd338 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Jan 2017 22:00:38 +0100 Subject: [PATCH 0808/1650] Consolidate common locals setup (fixes #665) Signed-off-by: Sebastian Ramacher --- bpython/cli.py | 4 ---- bpython/curtsiesfrontend/interpreter.py | 5 ----- bpython/repl.py | 17 ++++++++++++++++- bpython/urwid.py | 11 ++++------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index f4334b702..4c71957bc 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -58,7 +58,6 @@ import unicodedata import errno -from types import ModuleType from six.moves import range # These are used for syntax highlighting @@ -1891,9 +1890,6 @@ def main_curses(scr, args, config, interactive=True, locals_=None, curses.raw(True) main_win, statusbar = init_wins(scr, config) - if locals_ is None: - sys.modules['__main__'] = ModuleType('__main__') - locals_ = sys.modules['__main__'].__dict__ interpreter = repl.Interpreter(locals_, getpreferredencoding()) clirepl = CLIRepl(main_win, interpreter, statusbar, config, idle) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 223fe9618..37edf40e7 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -64,11 +64,6 @@ class Interp(ReplInterpreter): def __init__(self, locals=None, encoding=None): """Constructor. - The optional 'locals' argument specifies the dictionary in - which code will be executed; it defaults to a newly created - dictionary with key "__name__" set to "__console__" and key - "__doc__" set to None. - We include an argument for the outfile to pass to the formatter for it to write to. """ diff --git a/bpython/repl.py b/bpython/repl.py index 03c4debf7..9d73d3847 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -39,6 +39,7 @@ import traceback from itertools import takewhile from six import itervalues +from types import ModuleType from pygments.token import Token @@ -77,9 +78,16 @@ def estimate(self): class Interpreter(code.InteractiveInterpreter): + """Source code interpreter for use in bpython.""" def __init__(self, locals=None, encoding=None): - """The syntaxerror callback can be set at any time and will be called + """Constructor. + + The optional 'locals' argument specifies the dictionary in which code + will be executed; it defaults to a newly created dictionary with key + "__name__" set to "__main__". + + The syntaxerror callback can be set at any time and will be called on a caught syntax error. The purpose for this in bpython is so that the repl can be instantiated after the interpreter (which it necessarily must be with the current factoring) and then an exception @@ -95,6 +103,13 @@ def __init__(self, locals=None, encoding=None): self.encoding = encoding or sys.getdefaultencoding() self.syntaxerror_callback = None + + if locals is None: + # instead of messing with sys.modules, we should modify sys.modules + # in the interpreter instance + sys.modules['__main__'] = main_mod = ModuleType('__main__') + locals = main_mod.__dict__ + # Unfortunately code.InteractiveInterpreter is a classic class, so no # super() code.InteractiveInterpreter.__init__(self, locals) diff --git a/bpython/urwid.py b/bpython/urwid.py index ceb8717b8..dcea20be0 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -40,7 +40,6 @@ import time import locale import signal -from types import ModuleType from optparse import Option from six.moves import range from six import iteritems, string_types @@ -1192,11 +1191,7 @@ def main(args=None, locals_=None, banner=None): event_loop = None # TODO: there is also a glib event loop. Do we want that one? - # __main__ construction from bpython.cli - if locals_ is None: - main_mod = sys.modules['__main__'] = ModuleType('__main__') - locals_ = main_mod.__dict__ - + extend_locals = {} if options.plugin: try: from twisted import plugin @@ -1215,10 +1210,12 @@ def main(args=None, locals_=None, banner=None): plugopts = plug.options() plugopts.parseOptions(exec_args) serv = plug.makeService(plugopts) - locals_['service'] = serv + extend_locals['service'] = serv reactor.callWhenRunning(serv.startService) exec_args = [] interpreter = repl.Interpreter(locals_, locale.getpreferredencoding()) + # TODO: replace with something less hack-ish + interpreter.locals.update(extend_locals) # This nabs sys.stdin/out via urwid.MainLoop myrepl = URWIDRepl(event_loop, palette, interpreter, config) From a64fb8ebb9e6c3b572b395fb5b3560abb508ff6f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Jan 2017 22:38:43 +0100 Subject: [PATCH 0809/1650] Fix typo Signed-off-by: Sebastian Ramacher --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f7385b257..5d023875e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -418,7 +418,7 @@ A quick bugfix release (this should not become a habit). A bugfix/feature release (and a start at gtk). Happy Christmas everyone! * #67: Make pastebin URL really configurable. -* #68: Set a__main__ module and set interpreter's namespace to that module. +* #68: Set a __main__ module and set interpreter's namespace to that module. * #70: Implement backward completion on backward tab. * #62: Hide matches starting with a _ unless explicitly typed. * #72: Auto dedentation From 015356b0e142b1e0a95de35d6f6ceaf8871218ab Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Jan 2017 22:41:55 +0100 Subject: [PATCH 0810/1650] Update changelog --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 5d023875e..3833a971c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,10 @@ Changelog New features: Fixes: +* #648: Fix paste helper. + Thanks to Jakob Bowyer. +* #653: Handle docstrings more carefully. +* #654: Do not modify history file during tests. 0.16 ---- From a4aea3d8334c7e77226f5f7faba2d42cd4ce1054 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 9 Jan 2017 00:14:42 +0100 Subject: [PATCH 0811/1650] Re-use version_banner --- bpython/args.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index db5569d25..fbba65827 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -89,8 +89,7 @@ def parse(args, extras=None, ignore_stdin=False): os.execv(sys.executable, [sys.executable] + args) if options.version: - print('bpython version', __version__, end=" ") - print('on top of Python', sys.version.split()[0]) + print(version_banner()) print('(C) 2008-2016 Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al. ' 'See AUTHORS for detail.') raise SystemExit From 508082a8f607b4454625ebf662e6ff47795ced93 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 9 Jan 2017 00:16:18 +0100 Subject: [PATCH 0812/1650] Import consistency --- bpython/config.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index bd78c399b..3d02b236a 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -10,8 +10,7 @@ from six.moves.configparser import ConfigParser from bpython.keys import cli_key_dispatch as key_dispatch -from bpython.autocomplete import SIMPLE as default_completion -import bpython.autocomplete +from bpython.autocomplete import SIMPLE as default_completion, ALL_MODES class Struct(object): @@ -273,7 +272,7 @@ def get_key_no_doublebind(command): struct.hist_file = os.path.expanduser(struct.hist_file) # verify completion mode - if struct.autocomplete_mode not in bpython.autocomplete.ALL_MODES: + if struct.autocomplete_mode not in ALL_MODES: struct.autocomplete_mode = default_completion # set box drawing characters From 4029ab26ebf9a70612f20eefce9e8d7edfe2156a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 9 Jan 2017 00:40:16 +0100 Subject: [PATCH 0813/1650] Use consistent encoding Signed-off-by: Sebastian Ramacher --- bpdb/__init__.py | 2 ++ bpdb/__main__.py | 2 ++ bpdb/debugger.py | 2 ++ bpython/_internal.py | 2 ++ bpython/args.py | 2 ++ bpython/clipboard.py | 2 ++ bpython/curtsies.py | 2 ++ bpython/curtsiesfrontend/_internal.py | 2 ++ bpython/curtsiesfrontend/coderunner.py | 2 ++ bpython/curtsiesfrontend/events.py | 2 ++ bpython/curtsiesfrontend/filewatch.py | 2 ++ bpython/curtsiesfrontend/interaction.py | 2 ++ bpython/curtsiesfrontend/interpreter.py | 2 ++ bpython/curtsiesfrontend/manual_readline.py | 2 ++ bpython/curtsiesfrontend/preprocess.py | 2 ++ bpython/curtsiesfrontend/sitefix.py | 2 ++ bpython/formatter.py | 2 ++ bpython/importcompletion.py | 2 ++ bpython/keys.py | 2 ++ bpython/lazyre.py | 2 ++ bpython/line.py | 2 ++ bpython/pager.py | 2 ++ bpython/patch_linecache.py | 2 ++ bpython/test/test_bpython.py | 0 bpython/test/test_config.py | 2 ++ bpython/test/test_crashers.py | 2 ++ bpython/test/test_curtsies_coderunner.py | 2 ++ bpython/test/test_curtsies_painting.py | 1 + bpython/test/test_curtsies_parser.py | 2 ++ bpython/test/test_filewatch.py | 2 ++ bpython/test/test_history.py | 2 ++ bpython/test/test_importcompletion.py | 2 ++ bpython/test/test_keys.py | 2 ++ bpython/test/test_line_properties.py | 2 ++ bpython/test/test_manual_readline.py | 2 ++ bpython/test/test_pager.py | 0 bpython/test/test_preprocess.py | 2 ++ bpython/test/test_repl.py | 2 ++ bpython/urwid.py | 1 + 39 files changed, 72 insertions(+) delete mode 100644 bpython/test/test_bpython.py delete mode 100644 bpython/test/test_pager.py diff --git a/bpdb/__init__.py b/bpdb/__init__.py index 9f1c57d06..db2daba21 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + # The MIT License # # Copyright (c) 2008 Bob Farrell diff --git a/bpdb/__main__.py b/bpdb/__main__.py index b85accb45..c339c9c36 100644 --- a/bpdb/__main__.py +++ b/bpdb/__main__.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + # The MIT License # # Copyright (c) 2013 Sebastian Ramacher diff --git a/bpdb/debugger.py b/bpdb/debugger.py index 373389146..73e1c560b 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + # The MIT License # # Copyright (c) 2008 Bob Farrell diff --git a/bpython/_internal.py b/bpython/_internal.py index 225133ead..ea8fb9b33 100644 --- a/bpython/_internal.py +++ b/bpython/_internal.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import pydoc import sys diff --git a/bpython/args.py b/bpython/args.py index fbba65827..857c42618 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + """ Module to handle command line argument parsing, for all front-ends. """ diff --git a/bpython/clipboard.py b/bpython/clipboard.py index 252f1a225..5e426e381 100644 --- a/bpython/clipboard.py +++ b/bpython/clipboard.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + # The MIT License # # Copyright (c) 2015 Sebastian Ramacher diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 9f7f6d57d..ef38005f0 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + from __future__ import absolute_import import collections diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 187427824..7d458e646 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + # The MIT License # # Copyright (c) 2015 the bpython authors. diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index fc1e9dac6..7255a99cd 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + """For running Python code that could interrupt itself at any time in order to, for example, ask for a read on stdin, or a write on stdout diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index 982c96655..065a8dc41 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + """Non-keyboard events used in bpython curtsies REPL""" import time diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 301de9ac0..576852c03 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import os from collections import defaultdict diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 94ce6a3cf..5505e367f 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + from __future__ import unicode_literals import greenlet diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 37edf40e7..e1a484802 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import sys from six import iteritems, text_type diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 3849a95b8..953471e22 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + """implementations of simple readline edit operations just the ones that fit the model of transforming the current line diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index 473a0c346..279d25d9e 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + """Tools for preparing code to be run in the REPL (removing blank lines, etc)""" diff --git a/bpython/curtsiesfrontend/sitefix.py b/bpython/curtsiesfrontend/sitefix.py index a8cf8a38b..fa3016617 100644 --- a/bpython/curtsiesfrontend/sitefix.py +++ b/bpython/curtsiesfrontend/sitefix.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import sys from six.moves import builtins diff --git a/bpython/formatter.py b/bpython/formatter.py index 96ee6ed28..12885087a 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + # The MIT License # # Copyright (c) 2008 Bob Farrell diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index b23994908..4a283c527 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + # The MIT License # # Copyright (c) 2009-2011 Andreas Stuehrk diff --git a/bpython/keys.py b/bpython/keys.py index 619116a19..f8119b931 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + # The MIT License # # Copyright (c) 2008 Simon de Vlieger diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 54e8d565b..2729c8eb3 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + # The MIT License # # Copyright (c) 2015 Sebastian Ramacher diff --git a/bpython/line.py b/bpython/line.py index fecdfcec5..078059a4f 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + """Extracting and changing portions of the current line All functions take cursor offset from the beginning of the line and the line of diff --git a/bpython/pager.py b/bpython/pager.py index 70448672f..cf8944205 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + # The MIT License # # Copyright (c) 2009-2011 Andreas Stuehrk diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index 7dc321e7a..c335f05a4 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import linecache diff --git a/bpython/test/test_bpython.py b/bpython/test/test_bpython.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index 6b8c5dea4..fc5b841ed 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import os import tempfile import textwrap diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 341f6b45f..2e1ff4fda 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import fcntl import os import pty diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index 80af72760..b58806509 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import sys from bpython.test import mock, unittest diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 3f7414b9e..422d14cce 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -1,4 +1,5 @@ # coding: utf8 + from __future__ import unicode_literals import itertools import os diff --git a/bpython/test/test_curtsies_parser.py b/bpython/test/test_curtsies_parser.py index 0d765bab2..58d3b5424 100644 --- a/bpython/test/test_curtsies_parser.py +++ b/bpython/test/test_curtsies_parser.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + from __future__ import unicode_literals from bpython.test import unittest diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index 061e18b2b..9fe56b02d 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import os try: diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index abbe99d1f..49450e1b2 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + from six.moves import range from bpython.history import History diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 4df736b54..4b0c0fca3 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + from __future__ import unicode_literals from bpython import importcompletion diff --git a/bpython/test/test_keys.py b/bpython/test/test_keys.py index bf15542dd..1e19f8e5f 100644 --- a/bpython/test/test_keys.py +++ b/bpython/test/test_keys.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + from bpython import keys from bpython.test import unittest diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 4638084c2..cffd57241 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import re from bpython.test import unittest diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 8b87be651..4141292d2 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + from bpython.curtsiesfrontend.manual_readline import \ left_arrow, right_arrow, beginning_of_line, forward_word, back_word, \ end_of_line, delete, last_word_pos, backspace, delete_from_cursor_back, \ diff --git a/bpython/test/test_pager.py b/bpython/test/test_pager.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index f94366565..0ef21f098 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + from code import compile_command as compiler from functools import partial import difflib diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 34d7f774a..67a7d37a2 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + from itertools import islice from six.moves import range import collections diff --git a/bpython/urwid.py b/bpython/urwid.py index dcea20be0..7695c95e0 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1,3 +1,4 @@ +# encoding: utf-8 # # The MIT License From 5bd6bd070a5152bf1aba8eb5bfc541b4ec946589 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Jan 2017 21:55:23 +0100 Subject: [PATCH 0814/1650] Define encoding --- bpython/translations/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index 26c504075..84d75d808 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + import gettext import locale import os.path From af6bcae6c8bb5ab05a3e3634b6b815cdd1d41273 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Jan 2017 22:03:38 +0100 Subject: [PATCH 0815/1650] Change to [] for consistency --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 44495534c..6c6612bbc 100755 --- a/setup.py +++ b/setup.py @@ -255,7 +255,7 @@ def initialize_options(self): tests_require.append('mock') # translations -mo_files = list() +mo_files = [] for language in os.listdir(translations_dir): mo_subpath = os.path.join(language, 'LC_MESSAGES', 'bpython.mo') if os.path.exists(os.path.join(translations_dir, mo_subpath)): From 69c41088558ef2b5f6bc682c44d3f550765f915b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Jan 2017 22:05:37 +0100 Subject: [PATCH 0816/1650] Only check for 2.x since we only support >= 3.3 for 3.x --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6c6612bbc..5505f5ddd 100755 --- a/setup.py +++ b/setup.py @@ -250,8 +250,7 @@ def initialize_options(self): } tests_require = [] -if (sys.version_info[0] == 2 or - (sys.version_info[0] == 3 and sys.version_info[0] < 3)): +if sys.version_info[0] == 2: tests_require.append('mock') # translations From e8ed9dbd6200a990f07b69ef296c6aa7ce71f9e9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Jan 2017 22:20:23 +0100 Subject: [PATCH 0817/1650] Update requirements --- requirements.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7fd8f8ae4..78619ff24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ Pygments -curtsies >=0.1.15, <0.2.0 +curtsies >=0.1.18 greenlet -setuptools requests +setuptools +six >=1.5 From 8543e3c0e053c57e008143089dce7e4d84a5a90e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Jan 2017 22:20:31 +0100 Subject: [PATCH 0818/1650] Avoid duplication --- .travis.install.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index 8522aa261..dc568c48b 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -6,11 +6,7 @@ pip install setuptools if [[ $RUN == nosetests ]]; then # core dependencies - pip install pygments - pip install requests - pip install 'curtsies >=0.1.17' - pip install greenlet - pip install 'six >=1.5' + pop install -r requirements.txt # filewatch specific dependencies pip install watchdog # jedi specific dependencies From 7c8cef6fa6c2c66e37f497fdcccbf918a046b8c4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 10 Jan 2017 22:21:23 +0100 Subject: [PATCH 0819/1650] Fix typo --- .travis.install.sh | 2 +- bpython/__init__.py | 6 ++++-- bpython/_py3compat.py | 2 ++ bpython/args.py | 9 +++++---- bpython/autocomplete.py | 18 +++++++++--------- bpython/cli.py | 26 +++++++++++++------------- bpython/clipboard.py | 2 ++ bpython/config.py | 6 +++--- bpython/curtsies.py | 20 ++++++++++---------- bpython/filelock.py | 1 + bpython/formatter.py | 2 ++ bpython/history.py | 6 +++--- bpython/importcompletion.py | 8 +++++--- bpython/inspection.py | 7 ++++--- bpython/keys.py | 3 ++- bpython/lazyre.py | 2 ++ bpython/line.py | 4 ++-- bpython/pager.py | 1 + bpython/paste.py | 4 +++- bpython/patch_linecache.py | 2 ++ bpython/repl.py | 26 ++++++++++++++------------ bpython/simpleeval.py | 8 +++++--- bpython/simplerepl.py | 8 ++++---- bpython/translations/__init__.py | 6 ++++-- bpython/urwid.py | 17 +++++++---------- 25 files changed, 110 insertions(+), 86 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index dc568c48b..deaf5714a 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -6,7 +6,7 @@ pip install setuptools if [[ $RUN == nosetests ]]; then # core dependencies - pop install -r requirements.txt + pip install -r requirements.txt # filewatch specific dependencies pip install watchdog # jedi specific dependencies diff --git a/bpython/__init__.py b/bpython/__init__.py index 0fd94818f..a4f3bcc37 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -20,10 +20,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from __future__ import absolute_import + import os.path try: - from bpython._version import __version__ as version + from ._version import __version__ as version except ImportError: version = 'unknown' @@ -32,5 +34,5 @@ def embed(locals_=None, args=['-i', '-q'], banner=None): - from bpython.curtsies import main + from .curtsies import main return main(args, locals_, banner) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index 8082b8367..41acc5ce1 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -34,6 +34,8 @@ - py3: True if the hosting Python runtime is of Python version 3 or later """ +from __future__ import absolute_import + import sys py3 = (sys.version_info[0] == 3) diff --git a/bpython/args.py b/bpython/args.py index 857c42618..1dfe010df 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -4,16 +4,17 @@ Module to handle command line argument parsing, for all front-ends. """ -from __future__ import print_function +from __future__ import print_function, absolute_import + import code import imp import os import sys from optparse import OptionParser, OptionGroup -from bpython import __version__ -from bpython.config import default_config_path, loadini, Struct -from bpython.translations import _ +from . import __version__ +from .config import default_config_path, loadini, Struct +from .translations import _ class OptionParserFailed(ValueError): diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index fd4c2facb..d2ea220f1 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -23,7 +23,7 @@ # THE SOFTWARE. # -from __future__ import unicode_literals +from __future__ import unicode_literals, absolute_import import __main__ import abc @@ -37,14 +37,14 @@ from six.moves import range, builtins from six import string_types, iteritems -from bpython import inspection -from bpython import importcompletion -from bpython import line as lineparts -from bpython.line import LinePart -from bpython._py3compat import py3, try_decode -from bpython.lazyre import LazyReCompile -from bpython.simpleeval import (safe_eval, evaluate_current_expression, - EvaluationError) +from . import inspection +from . import importcompletion +from . import line as lineparts +from .line import LinePart +from ._py3compat import py3, try_decode +from .lazyre import LazyReCompile +from .simpleeval import (safe_eval, evaluate_current_expression, + EvaluationError) if not py3: from types import InstanceType, ClassType diff --git a/bpython/cli.py b/bpython/cli.py index 4c71957bc..c7a540283 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -39,7 +39,7 @@ # - Instead the suspend key exits the program # - View source doesn't work on windows unless you install the less program (From GnuUtils or Cygwin) -from __future__ import division +from __future__ import division, absolute_import import platform import os @@ -63,27 +63,27 @@ # These are used for syntax highlighting from pygments import format from pygments.formatters import TerminalFormatter -from bpython._py3compat import PythonLexer +from ._py3compat import PythonLexer from pygments.token import Token -from bpython.formatter import BPythonFormatter +from .formatter import BPythonFormatter # This for completion -from bpython import importcompletion +from . import importcompletion # This for config -from bpython.config import Struct, getpreferredencoding +from .config import Struct, getpreferredencoding # This for keys -from bpython.keys import cli_key_dispatch as key_dispatch +from .keys import cli_key_dispatch as key_dispatch # This for i18n -from bpython import translations -from bpython.translations import _ +from . import translations +from .translations import _ -from bpython import repl -from bpython._py3compat import py3 -from bpython.pager import page -import bpython.args +from . import repl +from ._py3compat import py3 +from .pager import page +from .args import parse as argsparse if not py3: import inspect @@ -1946,7 +1946,7 @@ def main(args=None, locals_=None, banner=None): translations.init() - config, options, exec_args = bpython.args.parse(args) + config, options, exec_args = argsparse(args) # Save stdin, stdout and stderr for later restoration orig_stdin = sys.stdin diff --git a/bpython/clipboard.py b/bpython/clipboard.py index 5e426e381..89a419f32 100644 --- a/bpython/clipboard.py +++ b/bpython/clipboard.py @@ -22,6 +22,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from __future__ import absolute_import + import subprocess import os import platform diff --git a/bpython/config.py b/bpython/config.py index 3d02b236a..a45afba7b 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -1,6 +1,6 @@ # encoding: utf-8 -from __future__ import unicode_literals +from __future__ import unicode_literals, absolute_import import os import sys @@ -9,8 +9,8 @@ from six import iterkeys, iteritems from six.moves.configparser import ConfigParser -from bpython.keys import cli_key_dispatch as key_dispatch -from bpython.autocomplete import SIMPLE as default_completion, ALL_MODES +from .autocomplete import SIMPLE as default_completion, ALL_MODES +from .keys import cli_key_dispatch as cli_key_dispatch class Struct(object): diff --git a/bpython/curtsies.py b/bpython/curtsies.py index ef38005f0..f4520ecfa 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -13,16 +13,16 @@ import curtsies.input import curtsies.events -from bpython.curtsiesfrontend.repl import BaseRepl -from bpython.curtsiesfrontend.coderunner import SystemExitFromCodeRunner -from bpython.curtsiesfrontend.interpreter import Interp -from bpython import args as bpargs -from bpython import translations -from bpython.translations import _ -from bpython.importcompletion import find_iterator -from bpython.curtsiesfrontend import events as bpythonevents -from bpython import inspection -from bpython.repl import extract_exit_value +from .curtsiesfrontend.repl import BaseRepl +from .curtsiesfrontend.coderunner import SystemExitFromCodeRunner +from .curtsiesfrontend.interpreter import Interp +from . import args as bpargs +from . import translations +from .translations import _ +from .importcompletion import find_iterator +from .curtsiesfrontend import events as bpythonevents +from . import inspection +from .repl import extract_exit_value logger = logging.getLogger(__name__) diff --git a/bpython/filelock.py b/bpython/filelock.py index 724cb4498..9ce5ee153 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -22,6 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from __future__ import absolute_import try: import fcntl diff --git a/bpython/formatter.py b/bpython/formatter.py index 12885087a..4d916c626 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -26,6 +26,8 @@ # Pygments really kicks ass, it made it really easy to # get the exact behaviour I wanted, thanks Pygments.:) +from __future__ import absolute_import + from pygments.formatter import Formatter from pygments.token import Keyword, Name, Comment, String, Error, \ Number, Operator, Token, Whitespace, Literal, Punctuation diff --git a/bpython/history.py b/bpython/history.py index 8093aca7a..bd9301b03 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -23,15 +23,15 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import unicode_literals +from __future__ import unicode_literals, absolute_import import io import os import stat from itertools import islice from six.moves import range -from bpython.translations import _ -from bpython.filelock import FileLock +from .translations import _ +from .filelock import FileLock class History(object): diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 4a283c527..5d5977b55 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -22,9 +22,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from bpython._py3compat import py3, try_decode -from bpython.line import current_word, current_import, \ - current_from_import_from, current_from_import_import +from __future__ import absolute_import + +from ._py3compat import py3, try_decode +from .line import (current_word, current_import, current_from_import_from, + current_from_import_import) import imp import os diff --git a/bpython/inspection.py b/bpython/inspection.py index 8ebb9496c..10b7db761 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -22,7 +22,8 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# + +from __future__ import absolute_import import inspect import io @@ -33,8 +34,8 @@ from pygments.token import Token -from bpython._py3compat import PythonLexer, py3 -from bpython.lazyre import LazyReCompile +from ._py3compat import PythonLexer, py3 +from .lazyre import LazyReCompile if not py3: import types diff --git a/bpython/keys.py b/bpython/keys.py index f8119b931..00c25b792 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -21,7 +21,8 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# + +from __future__ import absolute_import import string from six.moves import range diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 2729c8eb3..fa1cbd6ef 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -22,6 +22,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from __future__ import absolute_import + import re diff --git a/bpython/line.py b/bpython/line.py index 078059a4f..3a6806647 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -5,12 +5,12 @@ All functions take cursor offset from the beginning of the line and the line of Python code, and return None, or a tuple of the start index, end index, and the word.""" -from __future__ import unicode_literals +from __future__ import unicode_literals, absolute_import from itertools import chain from collections import namedtuple -from bpython.lazyre import LazyReCompile +from .lazyre import LazyReCompile LinePart = namedtuple('LinePart', ['start', 'stop', 'word']) diff --git a/bpython/pager.py b/bpython/pager.py index cf8944205..13e904a6b 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -22,6 +22,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from __future__ import absolute_import import curses import errno diff --git a/bpython/paste.py b/bpython/paste.py index 035812dcd..b76ab35c1 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -22,6 +22,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from __future__ import absolute_import + from locale import getpreferredencoding from six.moves.urllib_parse import quote as urlquote, urljoin, urlparse from string import Template @@ -30,7 +32,7 @@ import subprocess import unicodedata -from bpython.translations import _ +from .translations import _ class PasteFailed(Exception): diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index c335f05a4..2b473bca2 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -1,5 +1,7 @@ # encoding: utf-8 +from __future__ import absolute_import + import linecache diff --git a/bpython/repl.py b/bpython/repl.py index 9d73d3847..02e91404f 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -23,6 +23,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from __future__ import absolute_import + import code import inspect import io @@ -43,17 +45,17 @@ from pygments.token import Token -from bpython import autocomplete -from bpython import inspection -from bpython._py3compat import PythonLexer, py3, prepare_for_exec -from bpython.clipboard import get_clipboard, CopyFailed -from bpython.config import getpreferredencoding -from bpython.formatter import Parenthesis -from bpython.history import History -from bpython.paste import PasteHelper, PastePinnwand, PasteFailed -from bpython.patch_linecache import filename_for_console_input -from bpython.translations import _, ngettext -from bpython import simpleeval +from . import autocomplete +from . import inspection +from ._py3compat import PythonLexer, py3, prepare_for_exec +from .clipboard import get_clipboard, CopyFailed +from .config import getpreferredencoding +from .formatter import Parenthesis +from .history import History +from .paste import PasteHelper, PastePinnwand, PasteFailed +from .patch_linecache import filename_for_console_input +from .translations import _, ngettext +from . import simpleeval class RuntimeTimer(object): @@ -101,7 +103,7 @@ def __init__(self, locals=None, encoding=None): into a bytestring source as part of an encoding comment. """ - self.encoding = encoding or sys.getdefaultencoding() + self.encoding = encoding or getpreferredencoding() self.syntaxerror_callback = None if locals is None: diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index b7b348187..b4934fb66 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -28,6 +28,8 @@ """ +from __future__ import absolute_import + import ast import inspect from six import string_types @@ -35,9 +37,9 @@ import sys import types -from bpython import line as line_properties -from bpython._py3compat import py3 -from bpython.inspection import is_new_style, AttrCleaner +from . import line as line_properties +from ._py3compat import py3 +from .inspection import is_new_style, AttrCleaner _string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) _numeric_types = (int, float, complex) + (() if py3 else (long,)) diff --git a/bpython/simplerepl.py b/bpython/simplerepl.py index 4038cefdf..1355f270a 100644 --- a/bpython/simplerepl.py +++ b/bpython/simplerepl.py @@ -30,10 +30,10 @@ import time import logging -from bpython.curtsiesfrontend.repl import BaseRepl -from bpython.curtsiesfrontend import events as bpythonevents -from bpython import translations -from bpython import importcompletion +from .curtsiesfrontend.repl import BaseRepl +from .curtsiesfrontend import events as bpythonevents +from . import translations +from . import importcompletion from curtsies.configfile_keynames import keymap as key_dispatch diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index 84d75d808..4b3b2f956 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -1,12 +1,14 @@ # encoding: utf-8 +from __future__ import absolute_import + import gettext import locale import os.path import sys -from bpython import package_dir -from bpython._py3compat import py3 +from .. import package_dir +from .._py3compat import py3 translator = None diff --git a/bpython/urwid.py b/bpython/urwid.py index 7695c95e0..a23b7e406 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -47,13 +47,14 @@ from pygments.token import Token -from bpython import args as bpargs, repl, translations -from bpython._py3compat import py3 -from bpython.formatter import theme_map -from bpython.importcompletion import find_coroutine -from bpython.translations import _ +from . import args as bpargs, repl, translations +from ._py3compat import py3 +from .config import getpreferredencoding +from .formatter import theme_map +from .importcompletion import find_coroutine +from .translations import _ -from bpython.keys import urwid_key_dispatch as key_dispatch +from .keys import urwid_key_dispatch as key_dispatch import urwid @@ -83,10 +84,6 @@ } -def getpreferredencoding(): - return locale.getpreferredencoding() or "ascii" - - try: from twisted.internet import protocol from twisted.protocols import basic From 4c08efefea813aa58c1f1153de344d3f82bca396 Mon Sep 17 00:00:00 2001 From: shanahanjrs Date: Sun, 11 Dec 2016 23:00:37 -0500 Subject: [PATCH 0820/1650] Modified repl.py to conform to follow more PEP standards including 8 and 263 [Sebastian Ramacher]: Squashed 204bc17bc2005a0e3d040e5328601e36d7500508 and 9a0aedaed7ec89730714d495fd4b9806804ac242 and rebased on current master. --- bpython/repl.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 02e91404f..61e214fcd 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -59,6 +59,7 @@ class RuntimeTimer(object): + """Calculate running time""" def __init__(self): self.reset_timer() self.time = time.monotonic if hasattr(time, 'monotonic') else time.time @@ -224,7 +225,7 @@ def showtraceback(self): tblist[i] = (fname, lineno, module, something) # Set the right lineno (encoding header adds an extra line) if fname == '' and not py3: - tblist[i] = (fname, lineno - 2, module, something) + tblist[i] = (fname, lineno - 2, module, something) l = traceback.format_list(tblist) if l: @@ -787,13 +788,13 @@ def line_is_empty(line): indentation = 0 return indentation - def formatforfile(self, s): + def formatforfile(self, session_ouput): """Format the stdout buffer to something suitable for writing to disk, i.e. without >>> and ... at input lines and with "# OUT: " prepended to output lines.""" def process(): - for line in s.split('\n'): + for line in session_ouput.split('\n'): if line.startswith(self.ps1): yield line[len(self.ps1):] elif line.startswith(self.ps2): @@ -834,11 +835,11 @@ def write2file(self): self.interact.notify(_('Save cancelled.')) return - s = self.formatforfile(self.getstdout()) + session_test = self.formatforfile(self.getstdout()) try: with open(fn, mode) as f: - f.write(s) + f.write(stdout_text) except IOError as e: self.interact.notify(_("Error writing file '%s': %s") % (fn, e)) else: @@ -876,8 +877,8 @@ def do_pastebin(self, s): if s == self.prev_pastebin_content: self.interact.notify(_('Duplicate pastebin. Previous URL: %s. ' 'Removal URL: %s') % - (self.prev_pastebin_url, - self.prev_removal_url), 10) + (self.prev_pastebin_url, + self.prev_removal_url), 10) return self.prev_pastebin_url self.interact.notify(_('Posting data to pastebin...')) @@ -1088,7 +1089,7 @@ def clear_current_line(self): """This is used as the exception callback for the Interpreter instance. It prevents autoindentation from occurring after a traceback.""" - def send_to_external_editor(self, text, filename=None): + def send_to_external_editor(self, text): """Returns modified text from an editor, or the original text if editor exited with non-zero""" @@ -1117,7 +1118,7 @@ def open_in_external_editor(self, filename): return subprocess.call(args) == 0 def edit_config(self): - if not (os.path.isfile(self.config.config_path)): + if not os.path.isfile(self.config.config_path): if self.interact.confirm(_("Config file does not exist - create " "new from default? (y/N)")): try: @@ -1125,7 +1126,6 @@ def edit_config(self): 'sample-config') if py3: # py3 files need unicode default_config = default_config.decode('ascii') - bpython_dir, script_name = os.path.split(__file__) containing_dir = os.path.dirname( os.path.abspath(self.config.config_path)) if not os.path.exists(containing_dir): @@ -1159,10 +1159,10 @@ def next_indentation(line, tab_length): return indentation -def next_token_inside_string(s, inside_string): +def next_token_inside_string(code_string, inside_string): """Given a code string s and an initial state inside_string, return whether the next token will be inside a string or not.""" - for token, value in PythonLexer().get_tokens(s): + for token, value in PythonLexer().get_tokens(code_string): if token is Token.String: value = value.lstrip('bBrRuU') if value in ['"""', "'''", '"', "'"]: From fb47353bd6f11fbb4f001a726296a87a3c905b3b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 26 Jan 2017 20:04:29 +0100 Subject: [PATCH 0821/1650] Update to 3.6 Signed-off-by: Sebastian Ramacher --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3d761f016..a81e507eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: - "3.3" - "3.4" - "3.5" - - "3.6-dev" + - "3.6" - "pypy" - "pypy3" From 8ac18df3a82ff7f1b28fce7340a3948c8c928559 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 30 Mar 2017 00:29:04 -0700 Subject: [PATCH 0822/1650] fix default values of keyword only arguments --- bpython/urwid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index a23b7e406..c81843d9f 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -808,7 +808,7 @@ def _populate_completion(self): (color, arg)]) if arg in kwonly_defaults: markup.extend([('punctuation', '='), - ('token', kwonly_defaults[arg])]) + ('token', repr(kwonly_defaults[arg]))]) if varkw: if args or varargs or kwonly: From da6150b911f9e7caad0b25188b002e1c6d4045e0 Mon Sep 17 00:00:00 2001 From: periklis Date: Sat, 10 Dec 2016 17:51:26 +0200 Subject: [PATCH 0823/1650] Initial changes to implement ctrl+o functionality --- bpython/curtsiesfrontend/repl.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c45f0f2b7..3935741e1 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -483,13 +483,13 @@ def request_undo(self, n=1): """Like request_refresh, but for undo request events.""" raise NotImplementedError - def on_suspend(): + def on_suspend(self): """Will be called on sigtstp. Do whatever cleanup would allow the user to use other programs.""" raise NotImplementedError - def after_suspend(): + def after_suspend(self): """Will be called when process foregrounded after suspend. See to it that process_event is called with None to trigger a refresh @@ -675,6 +675,8 @@ def process_key_event(self, e): self.down_one_line() elif e in ("",): self.on_control_d() + elif e in ("",): + self.on_control_o() elif e in ("",): self.get_last_word() elif e in key_dispatch[self.config.reverse_incremental_search_key]: @@ -844,6 +846,13 @@ def on_control_d(self): self.current_line = (self.current_line[:self.cursor_offset] + self.current_line[(self.cursor_offset + 1):]) + def on_control_o(self): + next_idx = -self.rl_history.index + 1 + next = self.rl_history.entries[next_idx] + self.on_enter() + print("Next is : " + next) + self._set_current_line(next) + def cut_to_buffer(self): self.cut_buffer = self.current_line[self.cursor_offset:] self.current_line = self.current_line[:self.cursor_offset] @@ -958,11 +967,15 @@ def toggle_file_watch(self): # Handler Helpers def add_normal_character(self, char): + print("Char is :" + str(char)) if len(char) > 1 or is_nop(char): return if self.incr_search_mode: + print("Incr search mode") self.add_to_incremental_search(char) else: + print("In here current line: " + self.current_line) + self._set_current_line((self.current_line[:self.cursor_offset] + char + self.current_line[self.cursor_offset:]), @@ -970,6 +983,8 @@ def add_normal_character(self, char): reset_rl_history=False, clear_special_mode=False) self.cursor_offset += 1 + + print("but here: " + self.current_line) if (self.config.cli_trim_prompts and self.current_line.startswith(self.ps1)): self.current_line = self.current_line[4:] @@ -1021,6 +1036,7 @@ def push(self, line, insert_into_history=True): If the interpreter successfully runs the code, clear the buffer """ + if self.paste_mode: self.saved_indent = 0 else: @@ -1187,6 +1203,7 @@ def current_line_formatted(self): self.old_fs = fs return fs + @property def lines_for_display(self): """All display lines (wrapped, colored, with prompts)""" From e04e0f2c40037cddcef3c0a051d9eb2956275b60 Mon Sep 17 00:00:00 2001 From: periklis Date: Sat, 10 Dec 2016 17:55:48 +0200 Subject: [PATCH 0824/1650] Resetting debugging-oriented changes --- bpython/curtsiesfrontend/repl.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3935741e1..2cc03a594 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -967,15 +967,12 @@ def toggle_file_watch(self): # Handler Helpers def add_normal_character(self, char): - print("Char is :" + str(char)) if len(char) > 1 or is_nop(char): return if self.incr_search_mode: print("Incr search mode") self.add_to_incremental_search(char) else: - print("In here current line: " + self.current_line) - self._set_current_line((self.current_line[:self.cursor_offset] + char + self.current_line[self.cursor_offset:]), @@ -983,8 +980,6 @@ def add_normal_character(self, char): reset_rl_history=False, clear_special_mode=False) self.cursor_offset += 1 - - print("but here: " + self.current_line) if (self.config.cli_trim_prompts and self.current_line.startswith(self.ps1)): self.current_line = self.current_line[4:] @@ -1036,7 +1031,6 @@ def push(self, line, insert_into_history=True): If the interpreter successfully runs the code, clear the buffer """ - if self.paste_mode: self.saved_indent = 0 else: @@ -1203,7 +1197,6 @@ def current_line_formatted(self): self.old_fs = fs return fs - @property def lines_for_display(self): """All display lines (wrapped, colored, with prompts)""" From eb3b8f3277281e1131f6045fe54ae7e1eb3f9a17 Mon Sep 17 00:00:00 2001 From: periklis Date: Wed, 14 Dec 2016 23:13:06 +0200 Subject: [PATCH 0825/1650] Implementing code review changes. --- bpython/curtsiesfrontend/repl.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2cc03a594..db3f602b1 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -676,7 +676,7 @@ def process_key_event(self, e): elif e in ("",): self.on_control_d() elif e in ("",): - self.on_control_o() + self.operate_and_get_next() elif e in ("",): self.get_last_word() elif e in key_dispatch[self.config.reverse_incremental_search_key]: @@ -846,13 +846,6 @@ def on_control_d(self): self.current_line = (self.current_line[:self.cursor_offset] + self.current_line[(self.cursor_offset + 1):]) - def on_control_o(self): - next_idx = -self.rl_history.index + 1 - next = self.rl_history.entries[next_idx] - self.on_enter() - print("Next is : " + next) - self._set_current_line(next) - def cut_to_buffer(self): self.cut_buffer = self.current_line[self.cursor_offset:] self.current_line = self.current_line[:self.cursor_offset] @@ -860,6 +853,18 @@ def cut_to_buffer(self): def yank_from_buffer(self): pass + def operate_and_get_next(self): + # If we have not navigated back in history + # ctrl+o will have the same effect as enter + if self.rl_history.index == 0 or self.rl_history.index == 1: + self.on_enter() + return + + index = self.rl_history.index + self.on_enter() + self.rl_history.index = index + self._set_current_line(self.rl_history.entries[-index], reset_rl_history=False) + def up_one_line(self): self.rl_history.enter(self.current_line) self._set_current_line(tabs_to_spaces(self.rl_history.back( From f9ccb5c00115785a68651a987c6f873081f6eb0e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 12 Feb 2017 15:33:58 -0800 Subject: [PATCH 0826/1650] alternate impl for ctrl-o --- bpython/curtsiesfrontend/repl.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index db3f602b1..d29722c43 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -788,9 +788,11 @@ def readline_kill(self, e): else: self.cut_buffer = cut - def on_enter(self, insert_into_history=True): + def on_enter(self, insert_into_history=True, reset_rl_history=True): # so the cursor isn't touching a paren TODO: necessary? self._set_cursor_offset(-1, update_completion=False) + if reset_rl_history: + self.rl_history.reset() self.history.append(self.current_line) self.push(self.current_line, insert_into_history=insert_into_history) @@ -856,14 +858,7 @@ def yank_from_buffer(self): def operate_and_get_next(self): # If we have not navigated back in history # ctrl+o will have the same effect as enter - if self.rl_history.index == 0 or self.rl_history.index == 1: - self.on_enter() - return - - index = self.rl_history.index - self.on_enter() - self.rl_history.index = index - self._set_current_line(self.rl_history.entries[-index], reset_rl_history=False) + self.on_enter(reset_rl_history=False) def up_one_line(self): self.rl_history.enter(self.current_line) @@ -1091,7 +1086,11 @@ def run_code_and_maybe_finish(self, for_code=None): self.current_stdouterr_line, self.width)) self.current_stdouterr_line = '' - self._set_current_line(' ' * indent, update_completion=True) + if self.rl_history.index == 0: + self._set_current_line(' ' * indent, update_completion=True) + else: + self._set_current_line(self.rl_history.entries[-self.rl_history.index], + reset_rl_history=False) self.cursor_offset = len(self.current_line) def keyboard_interrupt(self): From 8e2723375fdb7403f6025cee1e4dc6009572affc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Almeida?= Date: Thu, 6 Apr 2017 17:27:09 -0300 Subject: [PATCH 0827/1650] Correct "next" duplication in line 40 --- doc/sphinx/source/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index e62b6b51c..a4f16f8da 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -37,7 +37,7 @@ Fork bpython in the GitHub web interface, then clone the repo: $ git clone git@github.com:YOUR_GITHUB_USERNAME/bpython.git # or "git clone https://github.com/YOUR_GITHUB_USERNAME/bpython.git" -Next install the install your development copy of bpython and its dependencies: +Next install your development copy of bpython and its dependencies: .. code-block:: bash From a743920e3fb2043a9610ed569dafc5b47932c559 Mon Sep 17 00:00:00 2001 From: Alwx Date: Tue, 18 Apr 2017 12:01:24 +0300 Subject: [PATCH 0828/1650] remove deprecation warning in inspect module method getargspec was deprecated in python3 and method getfullargspec was deprecated until python3.6 in favour of signature method --- bpython/curtsiesfrontend/manual_readline.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 953471e22..752ba8de8 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -10,10 +10,15 @@ import inspect from six import iteritems +from bpython._py3compat import py3 INDENT = 4 # TODO Allow user config of keybindings for these actions +if py3: + getargspec = lambda func: inspect.getargspec(func)[0] +else: + getargspec = lambda func: inspect.signature(func).parameters class AbstractEdits(object): @@ -38,7 +43,7 @@ def add(self, key, func, overwrite=False): del self[key] else: raise ValueError('key %r already has a mapping' % (key,)) - params = inspect.getargspec(func)[0] + params = getargspec(func) args = dict((k, v) for k, v in iteritems(self.default_kwargs) if k in params) r = func(**args) @@ -64,7 +69,7 @@ def add_config_attr(self, config_attr, func): def call(self, key, **kwargs): func = self[key] - params = inspect.getargspec(func)[0] + params = getargspec(func) args = dict((k, v) for k, v in kwargs.items() if k in params) return func(**args) From 951401a0ecaabda16aed5ce028c2329b17c2b088 Mon Sep 17 00:00:00 2001 From: Alwx Date: Tue, 18 Apr 2017 12:13:14 +0300 Subject: [PATCH 0829/1650] fix logic mistake --- bpython/curtsiesfrontend/manual_readline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 752ba8de8..35d28f541 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -15,7 +15,7 @@ INDENT = 4 # TODO Allow user config of keybindings for these actions -if py3: +if not py3: getargspec = lambda func: inspect.getargspec(func)[0] else: getargspec = lambda func: inspect.signature(func).parameters From e6214795e723a91e02ce7f56803f1b2efca8e4a6 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 22 Apr 2017 00:15:47 -0700 Subject: [PATCH 0830/1650] don't ignore rc flines by by overwriting rcLines --- bpdb/debugger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpdb/debugger.py b/bpdb/debugger.py index 73e1c560b..ecd011194 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -33,7 +33,6 @@ class BPdb(pdb.Pdb): def __init__(self, *args, **kwargs): pdb.Pdb.__init__(self, *args, **kwargs) - self.rcLines = [] self.prompt = '(BPdb) ' self.intro = 'Use "B" to enter bpython, Ctrl-d to exit it.' From ef917412711fe55fe64325dbb2c61872914d4ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20Sz=C3=B6ll=C5=91si?= Date: Wed, 10 May 2017 22:55:16 +0200 Subject: [PATCH 0831/1650] Use width aware slice --- bpython/curtsiesfrontend/replpainter.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 36c9c1324..a48d299fb 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -25,11 +25,14 @@ def display_linize(msg, columns, blank_line=False): """Returns lines obtained by splitting msg over multiple lines. Warning: if msg is empty, returns an empty list of lines""" - display_lines = ([msg[start:end] - for start, end in zip( - range(0, len(msg), columns), - range(columns, len(msg) + columns, columns))] - if msg else ([''] if blank_line else [])) + msg = fmtstr(msg) + try: + display_lines = [msg.width_aware_slice(slice(start, end)) + for start, end in zip( + range(0, msg.width, columns), + range(columns, msg.width + columns, columns))] + except ValueError: + display_lines = ([''] if blank_line else []) return display_lines From f0ca805b5e7a8ee6887cc5a25c078ffbf9bb6584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20Sz=C3=B6ll=C5=91si?= Date: Thu, 11 May 2017 14:00:33 +0200 Subject: [PATCH 0832/1650] Fix test failure --- bpython/curtsiesfrontend/replpainter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index a48d299fb..16babde9d 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -27,12 +27,13 @@ def display_linize(msg, columns, blank_line=False): Warning: if msg is empty, returns an empty list of lines""" msg = fmtstr(msg) try: - display_lines = [msg.width_aware_slice(slice(start, end)) + display_lines = ([msg.width_aware_slice(slice(start, end)) for start, end in zip( range(0, msg.width, columns), range(columns, msg.width + columns, columns))] + if msg else ([''] if blank_line else [])) except ValueError: - display_lines = ([''] if blank_line else []) + display_lines = [''] return display_lines From 724f3b9b561ce8b7397d8413a342c8b46262109d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20Sz=C3=B6ll=C5=91si?= Date: Sat, 27 May 2017 00:14:36 +0200 Subject: [PATCH 0833/1650] Fix newline handling in stdout and stderr --- bpython/curtsiesfrontend/repl.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d29722c43..a24d38c7e 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1079,13 +1079,6 @@ def run_code_and_maybe_finish(self, for_code=None): if err: indent = 0 - # TODO This should be printed ABOVE the error that just happened - # instead or maybe just thrown away and not shown - if self.current_stdouterr_line: - self.display_lines.extend(paint.display_linize( - self.current_stdouterr_line, self.width)) - self.current_stdouterr_line = '' - if self.rl_history.index == 0: self._set_current_line(' ' * indent, update_completion=True) else: @@ -1137,11 +1130,26 @@ def clear_current_block(self, remove_from_history=True): def get_current_block(self): return '\n'.join(self.buffer + [self.current_line]) + def move_current_stdouterr_line_up(self): + """Append self.current_stdouterr_line to self.display_lines + then clean it.""" + self.display_lines.extend(paint.display_linize( + self.current_stdouterr_line, self.width)) + self.current_stdouterr_line = '' + def send_to_stdout(self, output): """Send unicode string to Repl stdout""" + if not output: return lines = output.split('\n') + if output == len(output) * '\n': + # If the string consist only of newline characters, + # str.split returns one more empty strings. + lines = lines[:-1] logger.debug('display_lines: %r', self.display_lines) - self.current_stdouterr_line += lines[0] + if lines[0]: + self.current_stdouterr_line += lines[0] + else: + self.move_current_stdouterr_line_up() if len(lines) > 1: self.display_lines.extend(paint.display_linize( self.current_stdouterr_line, self.width, blank_line=True)) @@ -1157,9 +1165,16 @@ def send_to_stderr(self, error): Must be able to handle FmtStrs because interpreter pass in tracebacks already formatted.""" + if not error: return lines = error.split('\n') + if error == len(error) * '\n': + # If the string consist only of newline characters, + # str.split returns one more empty strings. + lines = lines[:-1] if lines[-1]: self.current_stdouterr_line += lines[-1] + else: + self.move_current_stdouterr_line_up() self.display_lines.extend(sum((paint.display_linize(line, self.width, blank_line=True) for line in lines[:-1]), [])) From 997117a7bc09a9f4ba2df200a31c853ea94cf012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20Sz=C3=B6ll=C5=91si?= Date: Sun, 28 May 2017 12:53:17 +0200 Subject: [PATCH 0834/1650] Changes requested by sebastinas --- bpython/curtsiesfrontend/repl.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index a24d38c7e..4f32abb93 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1139,9 +1139,10 @@ def move_current_stdouterr_line_up(self): def send_to_stdout(self, output): """Send unicode string to Repl stdout""" - if not output: return + if not output: + return lines = output.split('\n') - if output == len(output) * '\n': + if all(not line for line in lines): # If the string consist only of newline characters, # str.split returns one more empty strings. lines = lines[:-1] @@ -1165,9 +1166,10 @@ def send_to_stderr(self, error): Must be able to handle FmtStrs because interpreter pass in tracebacks already formatted.""" - if not error: return + if not error: + return lines = error.split('\n') - if error == len(error) * '\n': + if all(not line for line in lines): # If the string consist only of newline characters, # str.split returns one more empty strings. lines = lines[:-1] From 3463e7ac2bc1599b10a06bfbd89a4bf4a3dee009 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 May 2017 17:18:28 +0200 Subject: [PATCH 0835/1650] Update changelog Signed-off-by: Sebastian Ramacher --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 3833a971c..66e350388 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,12 +5,16 @@ Changelog ---- New features: +* #641: Implement Ctrol+O. Fixes: +* Fix deprecation warnings. * #648: Fix paste helper. Thanks to Jakob Bowyer. * #653: Handle docstrings more carefully. * #654: Do not modify history file during tests. +* #658: Fix newline handling. +* #670: Fix handlign of ANSI escape codes. 0.16 ---- From 8f86cc2356f719ea9c71b55df06fc2f853cf4384 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 May 2017 17:21:29 +0200 Subject: [PATCH 0836/1650] Fix typo Signed-off-by: Sebastian Ramacher --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 66e350388..74007f217 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,7 +5,7 @@ Changelog ---- New features: -* #641: Implement Ctrol+O. +* #641: Implement Ctrl+O. Fixes: * Fix deprecation warnings. From df00c79914c457433c2ddaf36da4354ea63d51af Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 May 2017 17:27:28 +0200 Subject: [PATCH 0837/1650] Error out if not enough parameters are given Signed-off-by: Sebastian Ramacher --- bpdb/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index db2daba21..4e0aef920 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -78,6 +78,10 @@ def main(): 'See AUTHORS for detail.') return 0 + if len(args) < 2: + print('usage: bpdb scriptfile [arg] ...') + return 2 + # The following code is based on Python's pdb.py. mainpyfile = args[1] if not os.path.exists(mainpyfile): From 670b5c9c38d4eaae635c6885f719fb9229b4ac39 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 May 2017 17:29:01 +0200 Subject: [PATCH 0838/1650] Use absolute imports --- bpdb/__init__.py | 4 ++-- bpdb/__main__.py | 4 +++- bpython/__main__.py | 4 +++- bpython/_internal.py | 4 +++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index 4e0aef920..5806a5c37 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -23,14 +23,14 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import print_function +from __future__ import print_function, absolute_import import os import sys import traceback import bpython -from bpdb.debugger import BPdb +from .debugger import BPdb from optparse import OptionParser from pdb import Restart diff --git a/bpdb/__main__.py b/bpdb/__main__.py index c339c9c36..758db27e7 100644 --- a/bpdb/__main__.py +++ b/bpdb/__main__.py @@ -22,8 +22,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from __future__ import absolute_import + import sys if __name__ == '__main__': - from bpdb import main + from . import main sys.exit(main()) diff --git a/bpython/__main__.py b/bpython/__main__.py index fe2651ebc..d81ec1efd 100644 --- a/bpython/__main__.py +++ b/bpython/__main__.py @@ -22,8 +22,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from __future__ import absolute_import + import sys if __name__ == '__main__': - from bpython.curtsies import main + from .curtsies import main sys.exit(main()) diff --git a/bpython/_internal.py b/bpython/_internal.py index ea8fb9b33..55da7e6d6 100644 --- a/bpython/_internal.py +++ b/bpython/_internal.py @@ -1,9 +1,11 @@ # encoding: utf-8 +from __future__ import absolute_import + import pydoc import sys -from bpython.pager import page +from .pager import page # Ugly monkeypatching pydoc.pager = page From 43c43dee3e341b941982f74f494e400d99efd47c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 May 2017 18:05:03 +0200 Subject: [PATCH 0839/1650] Avoid naming variables after global functions Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 61e214fcd..16b7a07a7 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -184,10 +184,10 @@ def showsyntaxerror(self, filename=None): if self.syntaxerror_callback is not None: self.syntaxerror_callback() - type, value, sys.last_traceback = sys.exc_info() - sys.last_type = type + exc_type, value, sys.last_traceback = sys.exc_info() + sys.last_type = exc_type sys.last_value = value - if filename and type is SyntaxError: + if filename and exc_type is SyntaxError: # Work hard to stuff the correct filename in the exception try: msg, (dummy_filename, lineno, offset, line) = value.args @@ -203,8 +203,8 @@ def showsyntaxerror(self, filename=None): lineno -= 2 value = SyntaxError(msg, (filename, lineno, offset, line)) sys.last_value = value - list = traceback.format_exception_only(type, value) - self.writetb(list) + exc_formatted = traceback.format_exception_only(exc_type, value) + self.writetb(exc_formatted) def showtraceback(self): """This needs to override the default traceback thing From 128e4b5d1fc06759c49cd85735f70ee8022f8a2a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 May 2017 18:09:23 +0200 Subject: [PATCH 0840/1650] Compile regex Signed-off-by: Sebastian Ramacher --- bpython/repl.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 16b7a07a7..fe3da2063 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -52,6 +52,7 @@ from .config import getpreferredencoding from .formatter import Parenthesis from .history import History +from .lazyre import LazyReCompile from .paste import PasteHelper, PastePinnwand, PasteFailed from .patch_linecache import filename_for_console_input from .translations import _, ngettext @@ -83,6 +84,8 @@ def estimate(self): class Interpreter(code.InteractiveInterpreter): """Source code interpreter for use in bpython.""" + bpython_input_re = LazyReCompile(r'') + def __init__(self, locals=None, encoding=None): """Constructor. @@ -197,7 +200,7 @@ def showsyntaxerror(self, filename=None): else: # Stuff in the right filename and right lineno # strip linecache line number - if re.match(r'', filename): + if self.bpython_input_re.match(filename): filename = '' if filename == '' and not py3: lineno -= 2 @@ -220,7 +223,7 @@ def showtraceback(self): for i, (fname, lineno, module, something) in enumerate(tblist): # strip linecache line number - if re.match(r'', fname): + if self.bpython_input_re.match(fname): fname = '' tblist[i] = (fname, lineno, module, something) # Set the right lineno (encoding header adds an extra line) From 6be4207a7da639581cd062b92de66618d0043fa9 Mon Sep 17 00:00:00 2001 From: Alex Frieder Date: Fri, 7 Jul 2017 21:00:08 -0400 Subject: [PATCH 0841/1650] Add default_autoreload config option --- bpython/config.py | 2 ++ bpython/curtsiesfrontend/repl.py | 5 ++++- bpython/sample-config | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index a45afba7b..d80ac7185 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -71,6 +71,7 @@ def loadini(struct, configfile): 'color_scheme': 'default', 'complete_magic_methods': True, 'dedent_after': 1, + 'default_autoreload': False, 'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')), 'flush_output': True, 'highlight_show_source': True, @@ -176,6 +177,7 @@ def get_key_no_doublebind(command): struct.hist_length = config.getint('general', 'hist_length') struct.hist_duplicates = config.getboolean('general', 'hist_duplicates') struct.flush_output = config.getboolean('general', 'flush_output') + struct.default_autoreload = config.getboolean('general', 'default_autoreload') struct.pastebin_key = get_key_no_doublebind('pastebin') struct.copy_clipboard_key = get_key_no_doublebind('copy_clipboard') diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 4f32abb93..9fd0745c8 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -420,7 +420,8 @@ def __init__(self, self.paste_mode = False self.current_match = None self.list_win_visible = False - self.watching_files = False # whether auto reloading active + # whether auto reloading active + self.watching_files = config.default_autoreload # 'reverse_incremental_search', 'incremental_search' or None self.incr_search_mode = None @@ -435,6 +436,8 @@ def __init__(self, self.status_bar.message(banner) self.watcher = ModuleChangedEventHandler([], self.request_reload) + if self.watcher and config.default_autoreload: + self.watcher.activate() # The methods below should be overridden, but the default implementations # below can be used as well. diff --git a/bpython/sample-config b/bpython/sample-config index 7fdf96361..1f7c8dd65 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -53,6 +53,9 @@ # always prompt. # single_undo_time = 1.0 +# Enable autoreload feature by default (default: False). +# default_autoreload = False + [keyboard] # All key bindings are shown commented out with their default binding From 51d208e1bbd25b028698f57ed4d701d2797e0da2 Mon Sep 17 00:00:00 2001 From: Max Nordlund Date: Tue, 18 Apr 2017 13:18:17 +0200 Subject: [PATCH 0842/1650] Allow bpython/bpdb outside main thread Because [signal.signal][1] is only allowed to be called from the main thread, if you use bpdb in another thread it will crash upon entering the repl. By ignoring the resulting `ValueError` the show can go on. [1]: https://docs.python.org/3/library/signal.html#signal.signal --- bpython/curtsiesfrontend/repl.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9fd0745c8..043b8ca51 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -531,8 +531,12 @@ def __enter__(self): sys.stdin = self.stdin self.orig_sigwinch_handler = signal.getsignal(signal.SIGWINCH) self.orig_sigtstp_handler = signal.getsignal(signal.SIGTSTP) - signal.signal(signal.SIGWINCH, self.sigwinch_handler) - signal.signal(signal.SIGTSTP, self.sigtstp_handler) + + try: + signal.signal(signal.SIGWINCH, self.sigwinch_handler) + signal.signal(signal.SIGTSTP, self.sigtstp_handler) + except ValueError: + pass # Ignore "signal only works in main thread" self.orig_meta_path = sys.meta_path if self.watcher: @@ -545,8 +549,13 @@ def __exit__(self, *args): sys.stdin = self.orig_stdin sys.stdout = self.orig_stdout sys.stderr = self.orig_stderr - signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) - signal.signal(signal.SIGTSTP, self.orig_sigtstp_handler) + + try: + signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) + signal.signal(signal.SIGTSTP, self.orig_sigtstp_handler) + except ValueError: + pass # Ignore "signal only works in main thread" + sys.meta_path = self.orig_meta_path def sigwinch_handler(self, signum, frame): From 4ba61b279d4733f57a9b8af2bd5d01f81c806139 Mon Sep 17 00:00:00 2001 From: Max Nordlund Date: Sun, 23 Apr 2017 11:32:28 +0200 Subject: [PATCH 0843/1650] Comment what is turned off This adds an extra comment explaining what is turned off when the signal handlers are ignored. --- bpython/curtsiesfrontend/repl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 043b8ca51..3f99ca18f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -536,7 +536,8 @@ def __enter__(self): signal.signal(signal.SIGWINCH, self.sigwinch_handler) signal.signal(signal.SIGTSTP, self.sigtstp_handler) except ValueError: - pass # Ignore "signal only works in main thread" + pass # Ignore "signal only works in main thread" + # This turns off resize detection and ctrl-z suspension. self.orig_meta_path = sys.meta_path if self.watcher: @@ -554,7 +555,8 @@ def __exit__(self, *args): signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) signal.signal(signal.SIGTSTP, self.orig_sigtstp_handler) except ValueError: - pass # Ignore "signal only works in main thread" + pass # Ignore "signal only works in main thread" + # This turns off resize detection and ctrl-z suspension. sys.meta_path = self.orig_meta_path From c58a933746df79af9daaa6fd8bd90180322ae1e7 Mon Sep 17 00:00:00 2001 From: Max Nordlund gmail Date: Mon, 24 Apr 2017 10:49:58 +0200 Subject: [PATCH 0844/1650] Use an if instead of a try statement --- bpython/curtsiesfrontend/repl.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3f99ca18f..c5ac8c51d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -11,6 +11,7 @@ import subprocess import sys import tempfile +import threading import time import unicodedata from six.moves import range @@ -532,12 +533,10 @@ def __enter__(self): self.orig_sigwinch_handler = signal.getsignal(signal.SIGWINCH) self.orig_sigtstp_handler = signal.getsignal(signal.SIGTSTP) - try: + if isinstance(threading.current_thread(), threading._MainThread): + # This turns off resize detection and ctrl-z suspension. signal.signal(signal.SIGWINCH, self.sigwinch_handler) signal.signal(signal.SIGTSTP, self.sigtstp_handler) - except ValueError: - pass # Ignore "signal only works in main thread" - # This turns off resize detection and ctrl-z suspension. self.orig_meta_path = sys.meta_path if self.watcher: @@ -551,12 +550,10 @@ def __exit__(self, *args): sys.stdout = self.orig_stdout sys.stderr = self.orig_stderr - try: + if isinstance(threading.current_thread(), threading._MainThread): + # This turns off resize detection and ctrl-z suspension. signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) signal.signal(signal.SIGTSTP, self.orig_sigtstp_handler) - except ValueError: - pass # Ignore "signal only works in main thread" - # This turns off resize detection and ctrl-z suspension. sys.meta_path = self.orig_meta_path From 307f855306ae8e0814458026b9fc47c1f25bf357 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 8 Jul 2017 10:47:47 +0200 Subject: [PATCH 0845/1650] Check if current thread is the main thread using threading.main_thread() Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c5ac8c51d..197a136b1 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -99,6 +99,14 @@ unicode = str +if sys.version_info >= (3, 4): + def is_main_thread(): + return threading.main_thread() == threading.current_thread() +else: + def is_main_thread(): + return isinstance(threading.current_thread(), threading._MainThread) + + class FakeStdin(object): """The stdin object user code will reference @@ -533,7 +541,7 @@ def __enter__(self): self.orig_sigwinch_handler = signal.getsignal(signal.SIGWINCH) self.orig_sigtstp_handler = signal.getsignal(signal.SIGTSTP) - if isinstance(threading.current_thread(), threading._MainThread): + if is_main_thread(): # This turns off resize detection and ctrl-z suspension. signal.signal(signal.SIGWINCH, self.sigwinch_handler) signal.signal(signal.SIGTSTP, self.sigtstp_handler) @@ -550,7 +558,7 @@ def __exit__(self, *args): sys.stdout = self.orig_stdout sys.stderr = self.orig_stderr - if isinstance(threading.current_thread(), threading._MainThread): + if is_main_thread(): # This turns off resize detection and ctrl-z suspension. signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) signal.signal(signal.SIGTSTP, self.orig_sigtstp_handler) From 836bb87d2a4f084ebbfffe3596e61e7bae20c891 Mon Sep 17 00:00:00 2001 From: Alex Frieder Date: Sat, 8 Jul 2017 10:29:04 -0400 Subject: [PATCH 0846/1650] Fix option-backspace behavior --- bpython/curtsiesfrontend/manual_readline.py | 2 +- bpython/test/test_manual_readline.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 35d28f541..223ec9e71 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -323,7 +323,7 @@ def titlecase_next_word(cursor_offset, line): return cursor_offset, line # TODO Not implemented -delete_word_from_cursor_back_re = LazyReCompile(r'\b\w') +delete_word_from_cursor_back_re = LazyReCompile(r'^|\b\w') @edit_keys.on('') diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 4141292d2..faf4b585f 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -240,6 +240,12 @@ def test_delete_word_from_cursor_back(self): "|"], delete_word_from_cursor_back) + self.try_stages_kill([ + " (( asdf |", + " (( |", + "|"], + delete_word_from_cursor_back) + class TestEdits(unittest.TestCase): From 08345670aea35e57b319483366f5a0a743dade20 Mon Sep 17 00:00:00 2001 From: Sorin Vatasoiu Date: Tue, 11 Jul 2017 18:30:37 -0700 Subject: [PATCH 0847/1650] fixed pasting (-p flag) --- bpython/curtsies.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index f4520ecfa..97fd7ebe1 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -32,8 +32,7 @@ class FullCurtsiesRepl(BaseRepl): - def __init__(self, config, locals_, banner, interp=None, - paste=None): + def __init__(self, config, locals_, banner, interp=None): self.input_generator = curtsies.input.Input( keynames='curtsies', sigint_event=True, @@ -189,7 +188,7 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): with repl.window as win: with repl: repl.height, repl.width = win.t.height, win.t.width - exit_value = repl.mainloop() + exit_value = repl.mainloop(True, paste) except (SystemExitFromCodeRunner, SystemExit) as e: exit_value = e.args return extract_exit_value(exit_value) From 99dcc9dc594736c239de9de971ca3fd572a17860 Mon Sep 17 00:00:00 2001 From: Tom Ballinger Date: Tue, 11 Jul 2017 22:23:21 -0700 Subject: [PATCH 0848/1650] fix paste fix --- bpython/curtsies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 97fd7ebe1..7af127d9f 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -182,7 +182,7 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): if banner is not None: print(banner) global repl - repl = FullCurtsiesRepl(config, locals_, welcome_message, interp, paste) + repl = FullCurtsiesRepl(config, locals_, welcome_message, interp) try: with repl.input_generator: with repl.window as win: From 9ec89db987844f98a6bba85c5680bd67182ef017 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 13 Jul 2017 22:29:47 +0200 Subject: [PATCH 0849/1650] Fix call of update Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 197a136b1..a4502fa58 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1029,7 +1029,7 @@ def update_completion(self, tab=False): # or disappear; whenever current line or cursor offset changes, # unless this happened via selecting a match self.current_match = None - self.list_win_visible = BpythonRepl.complete(self, tab) + self.list_win_visible = self.complete(tab) def predicted_indent(self, line): # TODO get rid of this! It's repeated code! Combine with Repl. From d067b9df7a54d3465a1199979c7fc2a608c8d43d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 13 Jul 2017 22:54:40 +0200 Subject: [PATCH 0850/1650] Decode matches from jedi (fixes #687) Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index d2ea220f1..f93d8b86d 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -519,7 +519,7 @@ def matches(self, cursor_offset, line, **kwargs): first_letter = line[self._orig_start:self._orig_start + 1] - matches = [c.name for c in completions] + matches = [try_decode(c.name, 'ascii') for c in completions] if any(not m.lower().startswith(matches[0][0].lower()) for m in matches): # Too general - giving completions starting with multiple From f9a76fdc1ae14253f5025f3a888e4a3f1b292762 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 13 Jul 2017 23:28:08 +0200 Subject: [PATCH 0851/1650] Update changelog --- CHANGELOG | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 74007f217..dfebfd864 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,15 +6,24 @@ Changelog New features: * #641: Implement Ctrl+O. +* Add default_autoreload config option. + Thanks to Alex Frieder. Fixes: * Fix deprecation warnings. +* Do not call signal outside of main thread. + Thanks to Max Nordlund. +* Fix option-backspace behavior. + Thanks to Alex Frieder. * #648: Fix paste helper. Thanks to Jakob Bowyer. * #653: Handle docstrings more carefully. * #654: Do not modify history file during tests. * #658: Fix newline handling. + Thanks to Attila Szöllősi. * #670: Fix handlign of ANSI escape codes. + Thanks to Attila Szöllősi. +* #687: Fix encoding of jedi completions. 0.16 ---- From 01b192154206d19703d56084435f96956a455bd3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 15 Jul 2017 11:48:09 +0200 Subject: [PATCH 0852/1650] Point Travis to 0.17-bugfix branch --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index ed68c8cbc..961e55c39 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ |ImageLink|_ -.. |ImageLink| image:: https://travis-ci.org/bpython/bpython.svg?branch=master +.. |ImageLink| image:: https://travis-ci.org/bpython/bpython.svg?branch=bugfix-0.17 .. _ImageLink: https://travis-ci.org/bpython/bpython *********************************************************************** From 14acea09f26e95dc02225ce9bbc45d70a763067f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 15 Jul 2017 11:49:34 +0200 Subject: [PATCH 0853/1650] Start development of 0.18 --- CHANGELOG | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index dfebfd864..685707112 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,15 @@ Changelog ========= +0.18 +---- + +New features: + + +Fixes: + + 0.17 ---- From 0cdaf41acfcc2830b2fe8cf81f28125c2197fd4b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 15 Jul 2017 12:46:35 +0200 Subject: [PATCH 0854/1650] Drop support for Python 3.3 By the time of the 0.18 release, Python 3.3 is EOL. Signed-off-by: Sebastian Ramacher --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a81e507eb..8fb607d80 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ notifications: python: - "2.7" - - "3.3" - "3.4" - "3.5" - "3.6" From 97a4c9d79a306be15b76a2d56cca80c5c4fa6096 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 15 Jul 2017 12:47:19 +0200 Subject: [PATCH 0855/1650] Test with 3.7-dev Signed-off-by: Sebastian Ramacher --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8fb607d80..be708063a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ python: - "3.4" - "3.5" - "3.6" + - "3.7-dev" - "pypy" - "pypy3" @@ -18,6 +19,7 @@ env: matrix: allow_failures: + - python: "3.7-dev" - python: "pypy" - python: "pypy3" From a0ef506012d829bcff34b254bdba6e54d7b8bbf9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 15 Jul 2017 15:18:25 +0200 Subject: [PATCH 0856/1650] Simplify version checks Signed-off-by: Sebastian Ramacher --- bpython/autocomplete.py | 3 +-- bpython/curtsiesfrontend/repl.py | 2 +- bpython/importcompletion.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index f93d8b86d..578a10bbb 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -33,7 +33,6 @@ import os import re import rlcompleter -import sys from six.moves import range, builtins from six import string_types, iteritems @@ -210,7 +209,7 @@ class FilenameCompletion(BaseCompletionType): def __init__(self, mode=SIMPLE): super(FilenameCompletion, self).__init__(False, mode) - if sys.version_info[:2] >= (3, 4): + if py3: def safe_glob(self, pathname): return glob.iglob(glob.escape(pathname) + '*') else: diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index a4502fa58..7497e7225 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -99,7 +99,7 @@ unicode = str -if sys.version_info >= (3, 4): +if py3: def is_main_thread(): return threading.main_thread() == threading.current_thread() else: diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 5d5977b55..b8dc1964c 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -35,7 +35,7 @@ from warnings import catch_warnings from six.moves import filter -if sys.version_info[0] == 3 and sys.version_info[1] >= 3: +if py3: import importlib.machinery SUFFIXES = importlib.machinery.all_suffixes() else: From 35beff8b687b051a84c0b252ad042e9e43c499d9 Mon Sep 17 00:00:00 2001 From: Tom Ballinger Date: Tue, 25 Jul 2017 18:27:35 -0700 Subject: [PATCH 0857/1650] simple fix (ctrl-c handling probably doesn't work in all cases) --- bpython/curtsiesfrontend/coderunner.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 7255a99cd..2fae023fb 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -13,6 +13,7 @@ """ import code +import threading import signal import greenlet import logging @@ -130,16 +131,19 @@ def run_code(self, for_code=None): if source code is complete, returns "done" if source code is incomplete, returns "unfinished" """ + is_main_thread = isinstance(threading.current_thread(), threading._MainThread) if self.code_context is None: assert self.source is not None self.code_context = greenlet.greenlet(self._blocking_run_code) - self.orig_sigint_handler = signal.getsignal(signal.SIGINT) - signal.signal(signal.SIGINT, self.sigint_handler) + if is_main_thread: + self.orig_sigint_handler = signal.getsignal(signal.SIGINT) + signal.signal(signal.SIGINT, self.sigint_handler) request = self.code_context.switch() else: assert self.code_is_waiting self.code_is_waiting = False - signal.signal(signal.SIGINT, self.sigint_handler) + if is_main_thread: + signal.signal(signal.SIGINT, self.sigint_handler) if self.sigint_happened_in_main_context: self.sigint_happened_in_main_context = False request = self.code_context.switch(SigintHappened) @@ -157,7 +161,8 @@ def run_code(self, for_code=None): return False elif isinstance(request, (Done, Unfinished)): self._unload_code() - signal.signal(signal.SIGINT, self.orig_sigint_handler) + if is_main_thread: + signal.signal(signal.SIGINT, self.orig_sigint_handler) self.orig_sigint_handler = None return request elif isinstance(request, SystemExitRequest): From 55c26557de4f9303eec1ce97b0c1b8fd5f167d98 Mon Sep 17 00:00:00 2001 From: Tom Ballinger Date: Sat, 19 Aug 2017 12:49:35 -0700 Subject: [PATCH 0858/1650] fix encoding issues in urwid and curses --- bpython/cli.py | 12 +++++++++--- bpython/urwid.py | 14 +++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index c7a540283..6fb0594ea 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -330,7 +330,7 @@ def __init__(self, scr, interp, statusbar, config, idle=None): repl.Repl.__init__(self, interp, config) self.interp.writetb = self.writetb self.scr = scr - self.stdout_hist = '' + self.stdout_hist = '' # str (bytes in Py2, unicode in Py3) self.list_win = newwin(get_colpair(config, 'background'), 1, 1, 1, 1) self.cpos = 0 self.do_exit = False @@ -1066,13 +1066,19 @@ def prompt(self, more): """Show the appropriate Python prompt""" if not more: self.echo("\x01%s\x03%s" % (self.config.color_scheme['prompt'], self.ps1)) - self.stdout_hist += self.ps1 + if py3: + self.stdout_hist += self.ps1 + else: + self.stdout_hist += self.ps1.encode(getpreferredencoding()) self.s_hist.append('\x01%s\x03%s\x04' % (self.config.color_scheme['prompt'], self.ps1)) else: prompt_more_color = self.config.color_scheme['prompt_more'] self.echo("\x01%s\x03%s" % (prompt_more_color, self.ps2)) - self.stdout_hist += self.ps2 + if py3: + self.stdout_hist += self.ps2 + else: + self.stdout_hist += self.ps2.encode(getpreferredencoding()) self.s_hist.append('\x01%s\x03%s\x04' % (prompt_more_color, self.ps2)) def push(self, s, insert_into_history=True): diff --git a/bpython/urwid.py b/bpython/urwid.py index c81843d9f..bcaf53460 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -596,7 +596,7 @@ def __init__(self, event_loop, palette, interpreter, config): self.tooltip = urwid.ListBox(urwid.SimpleListWalker([])) self.tooltip.grid = None self.overlay = Tooltip(self.listbox, self.tooltip) - self.stdout_hist = '' + self.stdout_hist = '' # str (bytes in Py2, unicode in Py3) self.frame = urwid.Frame(self.overlay) @@ -976,17 +976,17 @@ def prompt(self, more): # input to be the same type, using ascii as encoding. If the # caption is bytes this breaks typing non-ascii into bpython. if not more: + caption = ('prompt', self.ps1) if py3: - caption = ('prompt', self.ps1) + self.stdout_hist += self.ps1 else: - caption = ('prompt', self.ps1.decode(getpreferredencoding())) - self.stdout_hist += self.ps1 + self.stdout_hist += self.ps1.encode(getpreferredencoding()) else: + caption = ('prompt_more', self.ps2) if py3: - caption = ('prompt_more', self.ps2) + self.stdout_hist += self.ps2 else: - caption = ('prompt_more', self.ps2.decode(getpreferredencoding())) - self.stdout_hist += self.ps2 + self.stdout_hist += self.ps2.encode(getpreferredencoding()) self.edit = BPythonEdit(self.config, caption=caption) urwid.connect_signal(self.edit, 'change', self.on_input_change) From 143757c568bc016261cb5459586c9cb069697162 Mon Sep 17 00:00:00 2001 From: Tom Ballinger Date: Sun, 20 Aug 2017 16:11:55 -0700 Subject: [PATCH 0859/1650] terminology change: native str --- bpython/cli.py | 2 +- bpython/urwid.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 6fb0594ea..a8f9ba2f8 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -330,7 +330,7 @@ def __init__(self, scr, interp, statusbar, config, idle=None): repl.Repl.__init__(self, interp, config) self.interp.writetb = self.writetb self.scr = scr - self.stdout_hist = '' # str (bytes in Py2, unicode in Py3) + self.stdout_hist = '' # native str (bytes in Py2, unicode in Py3) self.list_win = newwin(get_colpair(config, 'background'), 1, 1, 1, 1) self.cpos = 0 self.do_exit = False diff --git a/bpython/urwid.py b/bpython/urwid.py index bcaf53460..acb153a27 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -596,7 +596,7 @@ def __init__(self, event_loop, palette, interpreter, config): self.tooltip = urwid.ListBox(urwid.SimpleListWalker([])) self.tooltip.grid = None self.overlay = Tooltip(self.listbox, self.tooltip) - self.stdout_hist = '' # str (bytes in Py2, unicode in Py3) + self.stdout_hist = '' # native str (bytes in Py2, unicode in Py3) self.frame = urwid.Frame(self.overlay) From a975340d8917d332e3ad3667c033d65f9b3d03b7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 14 Sep 2017 00:30:59 +0200 Subject: [PATCH 0860/1650] Move is_main_thread to bpython._py3compat Signed-off-by: Sebastian Ramacher --- bpython/_py3compat.py | 12 ++++++++++++ bpython/curtsiesfrontend/repl.py | 10 +--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index 41acc5ce1..c009ece5e 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -37,14 +37,17 @@ from __future__ import absolute_import import sys +import threading py3 = (sys.version_info[0] == 3) + if py3: from pygments.lexers import Python3Lexer as PythonLexer else: from pygments.lexers import PythonLexer + if py3 or sys.version_info[:3] >= (2, 7, 3): def prepare_for_exec(arg, encoding=None): return arg @@ -65,3 +68,12 @@ def try_decode(s, encoding): except UnicodeDecodeError: return None return s + + +if py3: + def is_main_thread(): + return threading.main_thread() == threading.current_thread() +else: + def is_main_thread(): + return isinstance(threading.current_thread(), threading._MainThread) + diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 7497e7225..58ad1713e 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -34,7 +34,7 @@ from bpython.formatter import BPythonFormatter from bpython import autocomplete from bpython.translations import _ -from bpython._py3compat import py3 +from bpython._py3compat import py3, is_main_thread from bpython.pager import get_pager_command from bpython.curtsiesfrontend import replpainter as paint @@ -99,14 +99,6 @@ unicode = str -if py3: - def is_main_thread(): - return threading.main_thread() == threading.current_thread() -else: - def is_main_thread(): - return isinstance(threading.current_thread(), threading._MainThread) - - class FakeStdin(object): """The stdin object user code will reference From 052b3a3620d90f3208f5593ddc63f7ce9f76e083 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 14 Sep 2017 00:31:31 +0200 Subject: [PATCH 0861/1650] Re-use is_main_thread Signed-off-by: Sebastian Ramacher --- bpython/curtsiesfrontend/coderunner.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 2fae023fb..647a7dca1 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -18,7 +18,7 @@ import greenlet import logging -from bpython._py3compat import py3 +from bpython._py3compat import py3, is_main_thread from bpython.config import getpreferredencoding logger = logging.getLogger(__name__) @@ -131,18 +131,17 @@ def run_code(self, for_code=None): if source code is complete, returns "done" if source code is incomplete, returns "unfinished" """ - is_main_thread = isinstance(threading.current_thread(), threading._MainThread) if self.code_context is None: assert self.source is not None self.code_context = greenlet.greenlet(self._blocking_run_code) - if is_main_thread: + if is_main_thread(): self.orig_sigint_handler = signal.getsignal(signal.SIGINT) signal.signal(signal.SIGINT, self.sigint_handler) request = self.code_context.switch() else: assert self.code_is_waiting self.code_is_waiting = False - if is_main_thread: + if is_main_thread(): signal.signal(signal.SIGINT, self.sigint_handler) if self.sigint_happened_in_main_context: self.sigint_happened_in_main_context = False @@ -161,7 +160,7 @@ def run_code(self, for_code=None): return False elif isinstance(request, (Done, Unfinished)): self._unload_code() - if is_main_thread: + if is_main_thread(): signal.signal(signal.SIGINT, self.orig_sigint_handler) self.orig_sigint_handler = None return request From 98a8269a8f38bcd473e0889097250d4733a46c2b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 9 Oct 2017 20:18:22 +0200 Subject: [PATCH 0862/1650] Fix appdata location Signed-off-by: Sebastian Ramacher --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5505f5ddd..a8ba03515 100755 --- a/setup.py +++ b/setup.py @@ -199,7 +199,7 @@ def initialize_options(self): # desktop shortcut (os.path.join('share', 'applications'), ['data/bpython.desktop']), # AppData - (os.path.join('share', 'appdata'), ['data/bpython.appdata.xml']), + (os.path.join('share', 'appinfo'), ['data/bpython.appdata.xml']), # icon (os.path.join('share', 'pixmaps'), ['data/bpython.png']) ] From a9c88a1c8db42d6bc6068caa5228c0882169f685 Mon Sep 17 00:00:00 2001 From: "cody.j.b.scott@gmail.com" Date: Tue, 10 Oct 2017 13:02:53 -0400 Subject: [PATCH 0863/1650] Add Python3 classifier Motivated by caniusepython3[0] reporting bpython doesn't support Python3. ```shell $ caniusepython3 --projects bpython ``` 0: https://pypi.python.org/pypi/caniusepython3 --- setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.py b/setup.py index a8ba03515..38cdab0ba 100755 --- a/setup.py +++ b/setup.py @@ -205,6 +205,10 @@ def initialize_options(self): ] data_files.extend(man_pages) +classifiers = [ + 'Programming Language :: Python :: 3', +] + install_requires = [ 'pygments', 'requests', @@ -270,6 +274,7 @@ def initialize_options(self): url="http://www.bpython-interpreter.org/", long_description="""bpython is a fancy interface to the Python interpreter for Unix-like operating systems.""", + classifiers=classifiers, install_requires=install_requires, extras_require=extras_require, tests_require=tests_require, From 2349722c9ce9b6f9d7ad5f411a1b5d2859a6b116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=91=D0=B5=D0=BD?= =?UTF-8?q?=D1=8C=D0=BA=D0=BE=D0=B2=D1=81=D0=BA=D0=B8=D0=B9?= Date: Thu, 19 Oct 2017 19:11:58 +0300 Subject: [PATCH 0864/1650] Fix NameError when saving to a file --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index fe3da2063..88a9b6e19 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -838,7 +838,7 @@ def write2file(self): self.interact.notify(_('Save cancelled.')) return - session_test = self.formatforfile(self.getstdout()) + stdout_text = self.formatforfile(self.getstdout()) try: with open(fn, mode) as f: From 1c5484f82580ffb6fc531562f07cfe41a8189b1d Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Mon, 6 Nov 2017 15:58:01 +0100 Subject: [PATCH 0865/1650] use self.assertEqual instead of self.assertEquals (deprecated) (#715) --- bpython/test/test_args.py | 2 +- bpython/test/test_config.py | 4 +- bpython/test/test_curtsies_parser.py | 6 +-- bpython/test/test_curtsies_repl.py | 8 ++-- bpython/test/test_interpreter.py | 6 +-- bpython/test/test_manual_readline.py | 62 ++++++++++++++-------------- bpython/test/test_simpleeval.py | 16 +++---- 7 files changed, 52 insertions(+), 52 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 9c89f73c9..52c613267 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -35,7 +35,7 @@ def test_exec_dunder_file(self): universal_newlines=True) (_, stderr) = p.communicate() - self.assertEquals(stderr.strip(), f.name) + self.assertEqual(stderr.strip(), f.name) def test_exec_nonascii_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index fc5b841ed..c4c3c8b83 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -30,13 +30,13 @@ def test_load_theme(self): struct.color_scheme = dict() config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, dict()) expected = {"keyword": "y"} - self.assertEquals(struct.color_scheme, expected) + self.assertEqual(struct.color_scheme, expected) defaults = {"name": "c"} expected.update(defaults) config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, defaults) - self.assertEquals(struct.color_scheme, expected) + self.assertEqual(struct.color_scheme, expected) def test_keybindings_default_contains_no_duplicates(self): struct = self.load_temp_config("") diff --git a/bpython/test/test_curtsies_parser.py b/bpython/test/test_curtsies_parser.py index 58d3b5424..019a7e0d4 100644 --- a/bpython/test/test_curtsies_parser.py +++ b/bpython/test/test_curtsies_parser.py @@ -10,15 +10,15 @@ class TestExecArgs(unittest.TestCase): def test_parse(self): - self.assertEquals(parse.parse('\x01y\x03print\x04'), yellow('print')) + self.assertEqual(parse.parse('\x01y\x03print\x04'), yellow('print')) - self.assertEquals( + self.assertEqual( parse.parse('\x01y\x03print\x04\x01c\x03 \x04\x01g\x031\x04\x01c' '\x03 \x04\x01Y\x03+\x04\x01c\x03 \x04\x01g\x032\x04'), yellow('print') + cyan(' ') + green('1') + cyan(' ') + bold(yellow('+')) + cyan(' ') + green(u'2')) def test_peal_off_string(self): - self.assertEquals(parse.peel_off_string('\x01RI\x03]\x04asdf'), + self.assertEqual(parse.peel_off_string('\x01RI\x03]\x04asdf'), ({'bg': 'I', 'string': ']', 'fg': 'R', 'colormarker': '\x01RI', 'bold': ''}, 'asdf')) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 3cf25da85..e91fff022 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -82,10 +82,10 @@ def test_get_last_word(self): self.assertEqual(self.repl.current_line, 'abcde3') def test_last_word(self): - self.assertEquals(curtsiesrepl._last_word(''), '') - self.assertEquals(curtsiesrepl._last_word(' '), '') - self.assertEquals(curtsiesrepl._last_word('a'), 'a') - self.assertEquals(curtsiesrepl._last_word('a b'), 'b') + self.assertEqual(curtsiesrepl._last_word(''), '') + self.assertEqual(curtsiesrepl._last_word(' '), '') + self.assertEqual(curtsiesrepl._last_word('a'), 'a') + self.assertEqual(curtsiesrepl._last_word('a b'), 'b') # this is the behavior of bash - not currently implemented @unittest.skip diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 95feb8b51..48cb8ad55 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -54,7 +54,7 @@ def test_syntaxerror(self): '\n') self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) - self.assertEquals(plain('').join(a), expected) + self.assertEqual(plain('').join(a), expected) def test_traceback(self): i, a = self.interp_errlog() @@ -79,7 +79,7 @@ def gfunc(): bold(red('NameError')) + ': ' + cyan(global_not_found) + '\n') self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) - self.assertEquals(plain('').join(a), expected) + self.assertEqual(plain('').join(a), expected) @unittest.skipIf(py3, "runsource() accepts only unicode in Python 3") def test_runsource_bytes(self): @@ -123,7 +123,7 @@ def test_getsource_works_on_interactively_defined_functions(self): i.runsource(source) import inspect inspected_source = inspect.getsource(i.locals['foo']) - self.assertEquals(inspected_source, source) + self.assertEqual(inspected_source, source) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_unicode_autoencode_and_noencode(self): diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index faf4b585f..705558d9a 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -20,7 +20,7 @@ def test_left_arrow_at_zero(self): pos = 0 expected = (pos, self._line) result = left_arrow(pos, self._line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_left_arrow_at_non_zero(self): for i in range(1, len(self._line)): @@ -32,25 +32,25 @@ def test_right_arrow_at_end(self): pos = len(self._line) expected = (pos, self._line) result = right_arrow(pos, self._line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_right_arrow_at_non_end(self): for i in range(len(self._line) - 1): expected = (i + 1, self._line) result = right_arrow(i, self._line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_beginning_of_line(self): expected = (0, self._line) for i in range(len(self._line)): result = beginning_of_line(i, self._line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_end_of_line(self): expected = (len(self._line), self._line) for i in range(len(self._line)): result = end_of_line(i, self._line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_forward_word(self): line = "going from here to_here" @@ -58,12 +58,12 @@ def test_forward_word(self): next_word_pos = 15 expected = (next_word_pos, line) result = forward_word(start_pos, line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) start_pos = 15 next_word_pos = 23 expected = (next_word_pos, line) result = forward_word(start_pos, line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_forward_word_tabs(self): line = "going from here to_here" @@ -71,12 +71,12 @@ def test_forward_word_tabs(self): next_word_pos = 15 expected = (next_word_pos, line) result = forward_word(start_pos, line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) start_pos = 15 next_word_pos = 28 expected = (next_word_pos, line) result = forward_word(start_pos, line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_forward_word_end(self): line = "going from here to_here" @@ -84,17 +84,17 @@ def test_forward_word_end(self): next_word_pos = 23 expected = (next_word_pos, line) result = forward_word(start_pos, line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) start_pos = 22 next_word_pos = 23 expected = (next_word_pos, line) result = forward_word(start_pos, line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) start_pos = 23 next_word_pos = 23 expected = (next_word_pos, line) result = forward_word(start_pos, line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_forward_word_empty(self): line = "" @@ -102,50 +102,50 @@ def test_forward_word_empty(self): next_word_pos = 0 expected = (next_word_pos, line) result = forward_word(start_pos, line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_back_word(self): line = "going to here from_here" start_pos = 14 prev_word_pos = 9 - self.assertEquals(line[start_pos], 'f') - self.assertEquals(line[prev_word_pos], 'h') + self.assertEqual(line[start_pos], 'f') + self.assertEqual(line[prev_word_pos], 'h') expected = (prev_word_pos, line) result = back_word(start_pos, line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_last_word_pos(self): line = "a word" expected = 2 result = last_word_pos(line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_last_word_pos_single_word(self): line = "word" expected = 0 result = last_word_pos(line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_delete(self): line = "deletion line" pos = 3 expected = (3, "deltion line") result = delete(pos, line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_delete_from_cursor_back(self): line = "everything before this will be deleted" expected = (0, "this will be deleted") result = delete_from_cursor_back(line.find("this"), line) - self.assertEquals(expected, result) + self.assertEqual(expected, result) def test_delete_from_cursor_forward(self): line = "everything after this will be deleted" pos = line.find("this") expected = (pos, "everything after ") result = delete_from_cursor_forward(line.find("this"), line)[:-1] - self.assertEquals(expected, result) - self.assertEquals(delete_from_cursor_forward(0, ''), (0, '', '')) + self.assertEqual(expected, result) + self.assertEqual(delete_from_cursor_forward(0, ''), (0, '', '')) def test_delete_rest_of_word(self): self.try_stages_kill([ @@ -184,7 +184,7 @@ def try_stages(self, strings, func): stages = [(s.index('|'), s.replace('|', '')) for s in strings] for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], stages[1:]): - self.assertEquals(func(initial_pos, initial), (final_pos, final)) + self.assertEqual(func(initial_pos, initial), (final_pos, final)) def try_stages_kill(self, strings, func): if not all('|' in s for s in strings): @@ -193,7 +193,7 @@ def try_stages_kill(self, strings, func): stages = [(s.index('|'), s.replace('|', '')) for s in strings] for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], stages[1:]): - self.assertEquals(func(initial_pos, initial)[:-1], + self.assertEqual(func(initial_pos, initial)[:-1], (final_pos, final)) def test_transpose_character_before_cursor(self): @@ -204,27 +204,27 @@ def test_transpose_character_before_cursor(self): "adf as|sdf"], transpose_character_before_cursor) def test_transpose_empty_line(self): - self.assertEquals(transpose_character_before_cursor(0, ''), + self.assertEqual(transpose_character_before_cursor(0, ''), (0, '')) def test_transpose_first_character(self): - self.assertEquals(transpose_character_before_cursor(0, 'a'), + self.assertEqual(transpose_character_before_cursor(0, 'a'), (0, 'a')) - self.assertEquals(transpose_character_before_cursor(0, 'as'), + self.assertEqual(transpose_character_before_cursor(0, 'as'), (0, 'as')) def test_transpose_end_of_line(self): - self.assertEquals(transpose_character_before_cursor(1, 'a'), + self.assertEqual(transpose_character_before_cursor(1, 'a'), (1, 'a')) - self.assertEquals(transpose_character_before_cursor(2, 'as'), + self.assertEqual(transpose_character_before_cursor(2, 'as'), (2, 'sa')) def test_transpose_word_before_cursor(self): pass def test_backspace(self): - self.assertEquals(backspace(2, 'as'), (1, 'a')) - self.assertEquals(backspace(3, 'as '), (2, 'as')) + self.assertEqual(backspace(2, 'as'), (1, 'a')) + self.assertEqual(backspace(3, 'as '), (2, 'as')) def test_delete_word_from_cursor_back(self): self.try_stages_kill([ diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 9db2fbf9b..92107c060 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -188,17 +188,17 @@ class TestSafeGetAttribute(unittest.TestCase): def test_lookup_on_object(self): a = A() a.x = 1 - self.assertEquals(safe_get_attribute_new_style(a, 'x'), 1) - self.assertEquals(safe_get_attribute_new_style(a, 'a'), 'a') + self.assertEqual(safe_get_attribute_new_style(a, 'x'), 1) + self.assertEqual(safe_get_attribute_new_style(a, 'a'), 'a') b = B() b.y = 2 - self.assertEquals(safe_get_attribute_new_style(b, 'y'), 2) - self.assertEquals(safe_get_attribute_new_style(b, 'a'), 'a') - self.assertEquals(safe_get_attribute_new_style(b, 'b'), 'b') + self.assertEqual(safe_get_attribute_new_style(b, 'y'), 2) + self.assertEqual(safe_get_attribute_new_style(b, 'a'), 'a') + self.assertEqual(safe_get_attribute_new_style(b, 'b'), 'b') def test_avoid_running_properties(self): p = Property() - self.assertEquals(safe_get_attribute_new_style(p, 'prop'), + self.assertEqual(safe_get_attribute_new_style(p, 'prop'), Property.prop) @unittest.skipIf(py3, 'Old-style classes not in Python 3') @@ -211,7 +211,7 @@ class Old: def test_lookup_with_slots(self): s = Slots() s.s1 = 's1' - self.assertEquals(safe_get_attribute(s, 's1'), 's1') + self.assertEqual(safe_get_attribute(s, 's1'), 's1') self.assertIsInstance(safe_get_attribute_new_style(s, 's1'), member_descriptor) with self.assertRaises(AttributeError): @@ -232,7 +232,7 @@ def test_lookup_with_property_and_slots(self): sga = safe_get_attribute s = SlotsSubclass() self.assertIsInstance(sga(Slots, 's3'), property) - self.assertEquals(safe_get_attribute(s, 's3'), + self.assertEqual(safe_get_attribute(s, 's3'), Slots.__dict__['s3']) self.assertIsInstance(sga(SlotsSubclass, 's3'), property) From fd5d4829d222ed69f4a00e7afa240f3898f877db Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Mon, 6 Nov 2017 23:52:10 +0100 Subject: [PATCH 0866/1650] test.test_args: don't hardcode python2 (#716) This will use python3 in a python3 env and python2 in a python2 one. --- bpython/test/test_args.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 52c613267..18e3abce4 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -40,7 +40,7 @@ def test_exec_dunder_file(self): def test_exec_nonascii_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write(dedent('''\ - #!/usr/bin/env python2 + #!/usr/bin/env python # coding: utf-8 "你好 # nonascii" ''')) @@ -55,7 +55,7 @@ def test_exec_nonascii_file(self): def test_exec_nonascii_file_linenums(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write(dedent("""\ - #!/usr/bin/env python2 + #!/usr/bin/env python # coding: utf-8 1/0 """)) From 0ddda1678c760c121e99ba6d4e55fac728e71d99 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 6 Nov 2017 23:53:04 +0100 Subject: [PATCH 0867/1650] Use sys.executable Signed-off-by: Sebastian Ramacher --- bpython/test/test_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 18e3abce4..04d362406 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -47,7 +47,7 @@ def test_exec_nonascii_file(self): f.flush() try: subprocess.check_call([ - 'python', '-m', 'bpython.curtsies', + sys.executable, '-m', 'bpython.curtsies', f.name]) except subprocess.CalledProcessError: self.fail('Error running module with nonascii characters') From b6b518be4761d3a477db3de41f32a88f51e600f5 Mon Sep 17 00:00:00 2001 From: "cody.j.b.scott@gmail.com" Date: Mon, 20 Nov 2017 17:21:57 -0500 Subject: [PATCH 0868/1650] Add Python2 classifier --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 38cdab0ba..709668165 100755 --- a/setup.py +++ b/setup.py @@ -206,6 +206,7 @@ def initialize_options(self): data_files.extend(man_pages) classifiers = [ + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', ] From 1222d909f55570f5a6a34b887c9b0d045152f431 Mon Sep 17 00:00:00 2001 From: toejough Date: Wed, 7 Feb 2018 17:32:23 -0500 Subject: [PATCH 0869/1650] expose global vars in debugger resolves https://github.com/bpython/bpython/issues/713 --- bpdb/debugger.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpdb/debugger.py b/bpdb/debugger.py index ecd011194..704af3f9e 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -44,7 +44,9 @@ def postloop(self): # cmd.Cmd commands def do_Bpython(self, arg): - bpython.embed(self.curframe.f_locals, ['-i']) + locals_ = dict(**self.curframe.f_globals, **self.curframe.f_locals) + bpython.embed(locals_, ['-i']) + def help_Bpython(self): print("B(python)") From d5abcdc176817be1f7fa8762b7d6ed4a049ad738 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Mon, 12 Feb 2018 22:07:29 +0000 Subject: [PATCH 0870/1650] Revert "Use width aware slice" Due to performance impact when outputting large strings this commit has to be temporarily reverted. This reverts commit ef917412711fe55fe64325dbb2c61872914d4ee0. --- bpython/curtsiesfrontend/replpainter.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 16babde9d..36c9c1324 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -25,15 +25,11 @@ def display_linize(msg, columns, blank_line=False): """Returns lines obtained by splitting msg over multiple lines. Warning: if msg is empty, returns an empty list of lines""" - msg = fmtstr(msg) - try: - display_lines = ([msg.width_aware_slice(slice(start, end)) - for start, end in zip( - range(0, msg.width, columns), - range(columns, msg.width + columns, columns))] - if msg else ([''] if blank_line else [])) - except ValueError: - display_lines = [''] + display_lines = ([msg[start:end] + for start, end in zip( + range(0, len(msg), columns), + range(columns, len(msg) + columns, columns))] + if msg else ([''] if blank_line else [])) return display_lines From 60be9a1f98e0f0864980cd7402d671cc23f5871e Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Mon, 12 Feb 2018 22:13:28 +0000 Subject: [PATCH 0871/1650] Revert "Use width aware slice" Reverted due to performance impact when outputting large strings. This reverts commit ef917412711fe55fe64325dbb2c61872914d4ee0. --- bpython/curtsiesfrontend/replpainter.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 16babde9d..36c9c1324 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -25,15 +25,11 @@ def display_linize(msg, columns, blank_line=False): """Returns lines obtained by splitting msg over multiple lines. Warning: if msg is empty, returns an empty list of lines""" - msg = fmtstr(msg) - try: - display_lines = ([msg.width_aware_slice(slice(start, end)) - for start, end in zip( - range(0, msg.width, columns), - range(columns, msg.width + columns, columns))] - if msg else ([''] if blank_line else [])) - except ValueError: - display_lines = [''] + display_lines = ([msg[start:end] + for start, end in zip( + range(0, len(msg), columns), + range(columns, len(msg) + columns, columns))] + if msg else ([''] if blank_line else [])) return display_lines From 6288c7f9905019192f1d5b3ce20ebffb1309fa1b Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Mon, 12 Feb 2018 22:15:23 +0000 Subject: [PATCH 0872/1650] Mark revert in CHANGELOG. --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index dfebfd864..9d9be8f53 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,13 @@ Changelog ========= +0.17.1 +------ + +Fixes: +* Reverted #670 temporarily due to performance impact + on large strings being output. + 0.17 ---- From 7ec25ac96f1607463007a5654bff24515a80feb7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Feb 2018 22:38:40 +0100 Subject: [PATCH 0873/1650] Require >= 3.4 for Python 3.x 3.3 reached its EOL in September 2017. Signed-off-by: Sebastian Ramacher --- doc/sphinx/source/contributing.rst | 2 +- doc/sphinx/source/releases.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index a4f16f8da..430aabc6e 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -17,7 +17,7 @@ the time of day. Getting your development environment set up ------------------------------------------- -bpython supports Python 2.7, 3.3 and newer. The code is compatible with all +bpython supports Python 2.7, 3.4 and newer. The code is compatible with all supported versions without the need to run post processing like `2to3`. Using a virtual environment is probably a good idea. Create a virtual diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst index 914360e7f..36a4a9bc8 100644 --- a/doc/sphinx/source/releases.rst +++ b/doc/sphinx/source/releases.rst @@ -45,7 +45,7 @@ A checklist to perform some manual tests before a release: Check that all of the following work before a release: -* Runs under Python 2.7, 3.3, 3.4 and 3.5. +* Runs under Python 2.7, 3.4, 3.5 and 3.6. * Save * Rewind * Pastebin From ab6f17846e858e3f4e77a4fe00cba44275f37786 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Feb 2018 22:52:33 +0100 Subject: [PATCH 0874/1650] Update changelog --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index a35d7f967..1aeb0098a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,10 @@ New features: * #713 expose globals in bpdb debugging. Thanks to toejough. +Fixes: + + +Support for Python 3.3 has been dropped. Fixes: From 23ee3c27e95f597ce1ad4ba4c58d7d534f9e2deb Mon Sep 17 00:00:00 2001 From: Tom Ballinger Date: Thu, 1 Mar 2018 09:43:23 -0800 Subject: [PATCH 0875/1650] remove debugging print in rev incr search --- bpython/curtsiesfrontend/repl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 58ad1713e..c4614a580 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -981,7 +981,6 @@ def add_normal_character(self, char): if len(char) > 1 or is_nop(char): return if self.incr_search_mode: - print("Incr search mode") self.add_to_incremental_search(char) else: self._set_current_line((self.current_line[:self.cursor_offset] + From 5698a9e291654751d3d7437c4ee650c062daf1b0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 5 Mar 2018 00:08:59 +0100 Subject: [PATCH 0876/1650] Fix compatibility with 2.7 Signed-off-by: Sebastian Ramacher --- bpdb/debugger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpdb/debugger.py b/bpdb/debugger.py index 704af3f9e..ba802070b 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -44,7 +44,8 @@ def postloop(self): # cmd.Cmd commands def do_Bpython(self, arg): - locals_ = dict(**self.curframe.f_globals, **self.curframe.f_locals) + locals_ = self.curframe.f_globals.copy() + locals_.update(self.curframe.f_locals) bpython.embed(locals_, ['-i']) From d4a28ef8f3a2a07454702e372c82bec14e599eed Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 5 Mar 2018 00:10:17 +0100 Subject: [PATCH 0877/1650] Exit gracefully if config file cannot be decoded (re #735) --- bpython/config.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index d80ac7185..a30e76c9b 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -137,13 +137,19 @@ def loadini(struct, configfile): in iteritems(defaults['keyboard'])) fill_config_with_default_values(config, defaults) - if not config.read(config_path): - # No config file. If the user has it in the old place then complain - if os.path.isfile(os.path.expanduser('~/.bpython.ini')): - sys.stderr.write("Error: It seems that you have a config file at " - "~/.bpython.ini. Please move your config file to " - "%s\n" % default_config_path()) - sys.exit(1) + try: + if not config.read(config_path): + # No config file. If the user has it in the old place then complain + if os.path.isfile(os.path.expanduser('~/.bpython.ini')): + sys.stderr.write("Error: It seems that you have a config file at " + "~/.bpython.ini. Please move your config file to " + "%s\n" % default_config_path()) + sys.exit(1) + except UnicodeDecodeError as e: + sys.stderr.write("Error: Unable to parse config file at '{}' due to an " + "encoding issue. Please make sure to fix the encoding " + "of the file or remove it and then try again.\n".format(config_path)) + sys.exit(1) def get_key_no_doublebind(command): default_commands_to_keys = defaults['keyboard'] From 616792bcdaa516d135f8f2ca6360817fe55cf620 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 5 Mar 2018 00:47:57 +0100 Subject: [PATCH 0878/1650] Adapt desktop and appdata files to new spec Signed-off-by: Sebastian Ramacher --- ...ta.xml => org.bpython-interpreter.bpython.appdata.xml} | 8 ++++---- ...on.desktop => org.bpython-interpreter.bpython.desktop} | 0 setup.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename data/{bpython.appdata.xml => org.bpython-interpreter.bpython.appdata.xml} (89%) rename data/{bpython.desktop => org.bpython-interpreter.bpython.desktop} (100%) diff --git a/data/bpython.appdata.xml b/data/org.bpython-interpreter.bpython.appdata.xml similarity index 89% rename from data/bpython.appdata.xml rename to data/org.bpython-interpreter.bpython.appdata.xml index 8bcf0e163..e24a09069 100644 --- a/data/bpython.appdata.xml +++ b/data/org.bpython-interpreter.bpython.appdata.xml @@ -1,8 +1,7 @@ - - - - bpython.desktop + + + org.bpython-interpreter.bpython CC0-1.0 MIT bpython interpreter @@ -22,6 +21,7 @@
      • Auto-indentation.
      + org.bpython-interpreter.bpython.desktop http://www.bpython-interpreter.org/ https://github.com/bpython/bpython/issues diff --git a/data/bpython.desktop b/data/org.bpython-interpreter.bpython.desktop similarity index 100% rename from data/bpython.desktop rename to data/org.bpython-interpreter.bpython.desktop diff --git a/setup.py b/setup.py index 709668165..f8f943339 100755 --- a/setup.py +++ b/setup.py @@ -197,9 +197,9 @@ def initialize_options(self): data_files = [ # desktop shortcut - (os.path.join('share', 'applications'), ['data/bpython.desktop']), + (os.path.join('share', 'applications'), ['data/org.bpython-interpreter.bpython.desktop']), # AppData - (os.path.join('share', 'appinfo'), ['data/bpython.appdata.xml']), + (os.path.join('share', 'appinfo'), ['data/org.bpython-interpreter.bpython.appdata.xml']), # icon (os.path.join('share', 'pixmaps'), ['data/bpython.png']) ] From 997c2bc4fbb576e1f2c6ba9aaf7955fcba54a08f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 5 Mar 2018 00:50:59 +0100 Subject: [PATCH 0879/1650] Fix paths Signed-off-by: Sebastian Ramacher --- MANIFEST.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 66034b0cd..badb93f1e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,8 +3,8 @@ include AUTHORS include CHANGELOG include LICENSE include data/bpython.png -include data/bpython.desktop -include data/bpython.appdata.xml +include data/org.bpython-interpreter.bpython.desktop +include data/org.bpython-interpreter.bpython.appdata.xml include doc/sphinx/source/conf.py include doc/sphinx/source/*.rst include doc/sphinx/source/logo.png From 1b2068903bd779ba59dc15e302d21faffb687a53 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 8 Mar 2018 21:25:30 +0100 Subject: [PATCH 0880/1650] Fix file locking on Windows Also rename fd to fileobj. Signed-off-by: Sebastian Ramacher --- bpython/filelock.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bpython/filelock.py b/bpython/filelock.py index 9ce5ee153..e956c5543 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -42,8 +42,8 @@ class BaseLock(object): """Base class for file locking """ - def __init__(self, fd): - self.fd = fd + def __init__(self, fileobj): + self.fileobj = fileobj self.locked = False def acquire(self): @@ -69,8 +69,8 @@ class UnixFileLock(BaseLock): """Simple file locking for Unix using fcntl """ - def __init__(self, fd, mode=None): - super(UnixFileLock, self).__init__(fd) + def __init__(self, fileobj, mode=None): + super(UnixFileLock, self).__init__(fileobj) if mode is None: mode = fcntl.LOCK_EX @@ -78,14 +78,14 @@ def __init__(self, fd, mode=None): def acquire(self): try: - fcntl.flock(self.fd, self.mode) + fcntl.flock(self.fileobj, self.mode) self.locked = True except IOError as e: if e.errno != errno.ENOLCK: raise e def release(self): - fcntl.flock(self.fd, fcntl.LOCK_UN) + fcntl.flock(self.fileobj, fcntl.LOCK_UN) self.locked = False @@ -93,15 +93,15 @@ class WindowsFileLock(BaseLock): """Simple file locking for Windows using msvcrt """ - def __init__(self, fd, mode=None): - super(WindowsFileLock, self).__init__(fd) + def __init__(self, fileobj, mode=None): + super(WindowsFileLock, self).__init__(fileobj) def acquire(self): - msvcrt.locking(self.fd, msvcrt.LK_NBLCK, 1) + msvcrt.locking(self.fileobj.fileno(), msvcrt.LK_NBLCK, 1) self.locked = True def release(self): - msvcrt.locking(self.fd, msvcrt.LK_UNLCK, 1) + msvcrt.locking(self.fileobj.fileno(), msvcrt.LK_UNLCK, 1) self.locked = False From 0ff009e16f66b4807b33d11d9efe3790c52b51dc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 8 Mar 2018 21:42:20 +0100 Subject: [PATCH 0881/1650] Update changelog --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 1aeb0098a..44db01884 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,7 +9,8 @@ New features: Thanks to toejough. Fixes: - +* Fix file locking on Windows. +* Exit gracefully if config file fails to be loaded due to encoding errors. Support for Python 3.3 has been dropped. From c85c4e76af7808601ba260ff0d29b43e1c320b71 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 20 May 2018 23:24:24 +0200 Subject: [PATCH 0882/1650] Fix PEP8 issues --- bpython/_internal.py | 1 + bpython/_py3compat.py | 1 - bpython/cli.py | 70 +++++++++++++------------ bpython/config.py | 3 +- bpython/curtsies.py | 2 +- bpython/importcompletion.py | 1 + bpython/line.py | 6 +-- bpython/patch_linecache.py | 2 +- bpython/repl.py | 6 ++- bpython/simpleeval.py | 8 +-- bpython/urwid.py | 100 ++++++++++++++++++++---------------- 11 files changed, 113 insertions(+), 87 deletions(-) diff --git a/bpython/_internal.py b/bpython/_internal.py index 55da7e6d6..e0e87ba3c 100644 --- a/bpython/_internal.py +++ b/bpython/_internal.py @@ -27,6 +27,7 @@ def __repr__(self): def __call__(self, *args, **kwargs): self.helper(*args, **kwargs) + _help = _Helper() diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index c009ece5e..5e6ff3aa8 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -76,4 +76,3 @@ def is_main_thread(): else: def is_main_thread(): return isinstance(threading.current_thread(), threading._MainThread) - diff --git a/bpython/cli.py b/bpython/cli.py index a8f9ba2f8..2a134d95b 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -52,9 +52,9 @@ import struct if platform.system() != 'Windows': - import signal #Windows does not have job control - import termios #Windows uses curses - import fcntl #Windows uses curses + import signal # Windows does not have job control + import termios # Windows uses curses + import fcntl # Windows uses curses import unicodedata import errno @@ -112,6 +112,7 @@ def calculate_screen_lines(tokens, width, cursor=0): pos %= width return lines + def forward_if_not_current(func): @functools.wraps(func) def newfunc(self, *args, **kwargs): @@ -202,7 +203,7 @@ def readline(self, size=-1): # C-d return '' elif (key not in ('\n', '\r') and - (len(key) > 1 or unicodedata.category(key) == 'Cc')): + (len(key) > 1 or unicodedata.category(key) == 'Cc')): continue sys.stdout.write(key) # Include the \n in the buffer - raw_input() seems to deal with trailing @@ -281,7 +282,7 @@ def make_colors(config): if platform.system() == 'Windows': c = dict(list(c.items()) + - [ + [ ('K', 8), ('R', 9), ('G', 10), @@ -290,8 +291,8 @@ def make_colors(config): ('M', 13), ('C', 14), ('W', 15), - ] - ) + ] + ) for i in range(63): if i > 7: @@ -316,7 +317,6 @@ def confirm(self, q): return reply.lower() in (_('y'), _('yes')) - def notify(self, s, n=10, wait_for_keypress=False): return self.statusbar.message(s, n) @@ -350,6 +350,7 @@ def __init__(self, scr, interp, statusbar, config, idle=None): def _get_cursor_offset(self): return len(self.s) - self.cpos + def _set_cursor_offset(self, offset): self.cpos = len(self.s) - offset cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, @@ -427,7 +428,7 @@ def check(self): it and force syntax highlighting.""" if (self.paste_mode - and time.time() - self.last_key_press > self.config.paste_time): + and time.time() - self.last_key_press > self.config.paste_time): self.paste_mode = False self.print_line(self.s) @@ -454,7 +455,7 @@ def complete(self, tab=False): with tab """ if self.paste_mode: - self.scr.touchwin() #TODO necessary? + self.scr.touchwin() # TODO necessary? return list_win_visible = repl.Repl.complete(self, tab) @@ -489,6 +490,7 @@ def clrtobol(self): def _get_current_line(self): return self.s + def _set_current_line(self, line): self.s = line current_line = property(_get_current_line, _set_current_line, None, @@ -716,7 +718,7 @@ def mkargspec(self, topline, in_arg, down): self.list_win.addstr('\n ') self.list_win.addstr(fn, - get_colpair(self.config, 'name') | curses.A_BOLD) + get_colpair(self.config, 'name') | curses.A_BOLD) self.list_win.addstr(': (', get_colpair(self.config, 'name')) maxh = self.scr.getmaxyx()[0] @@ -765,7 +767,7 @@ def mkargspec(self, topline, in_arg, down): if kw is not None: self.list_win.addstr('=', punctuation_colpair) self.list_win.addstr(kw, get_colpair(self.config, 'token')) - if k != len(args) -1: + if k != len(args) - 1: self.list_win.addstr(', ', punctuation_colpair) if _args: @@ -854,7 +856,7 @@ def p_key(self, key): key = '\n' # Don't return; let it get handled - if key == chr(27): #Escape Key + if key == chr(27): # Escape Key return '' if key in (BACKSP, 'KEY_BACKSPACE'): @@ -894,7 +896,7 @@ def p_key(self, key): self.fwd() return '' - elif key in ("KEY_LEFT",' ^B', chr(2)): # Cursor Left or ^B + elif key in ("KEY_LEFT", ' ^B', chr(2)): # Cursor Left or ^B self.mvc(1) # Redraw (as there might have been highlighted parens) self.print_line(self.s) @@ -914,11 +916,11 @@ def p_key(self, key): # Redraw (as there might have been highlighted parens) self.print_line(self.s) - elif key in ("KEY_NPAGE", '\T'): # page_down or \T + elif key in ("KEY_NPAGE", '\T'): # page_down or \T self.hend() self.print_line(self.s) - elif key in ("KEY_PPAGE", '\S'): # page_up or \S + elif key in ("KEY_PPAGE", '\S'): # page_up or \S self.hbegin() self.print_line(self.s) @@ -1065,7 +1067,8 @@ def print_line(self, s, clr=False, newline=False): def prompt(self, more): """Show the appropriate Python prompt""" if not more: - self.echo("\x01%s\x03%s" % (self.config.color_scheme['prompt'], self.ps1)) + self.echo("\x01%s\x03%s" % + (self.config.color_scheme['prompt'], self.ps1)) if py3: self.stdout_hist += self.ps1 else: @@ -1079,7 +1082,8 @@ def prompt(self, more): self.stdout_hist += self.ps2 else: self.stdout_hist += self.ps2.encode(getpreferredencoding()) - self.s_hist.append('\x01%s\x03%s\x04' % (prompt_more_color, self.ps2)) + self.s_hist.append('\x01%s\x03%s\x04' % + (prompt_more_color, self.ps2)) def push(self, s, insert_into_history=True): # curses.raw(True) prevents C-c from causing a SIGINT @@ -1103,7 +1107,7 @@ def redraw(self): self.iy, self.ix = self.scr.getyx() for i in s.split('\x04'): self.echo(i, redraw=False) - if k < len(self.s_hist) -1: + if k < len(self.s_hist) - 1: self.scr.addstr('\n') self.iy, self.ix = self.scr.getyx() self.print_line(self.s) @@ -1186,14 +1190,12 @@ def resize(self): self.statusbar.resize(refresh=False) self.redraw() - def getstdout(self): """This method returns the 'spoofed' stdout buffer, for writing to a file or sending to a pastebin or whatever.""" return self.stdout_hist + '\n' - def reevaluate(self): """Clear the buffer, redraw the screen and re-evaluate the history""" @@ -1234,7 +1236,7 @@ def reevaluate(self): self.evaluating = False #map(self.push, self.history) - #^-- That's how simple this method was at first :( + # ^-- That's how simple this method was at first :( def write(self, s): """For overriding stdout defaults""" @@ -1258,7 +1260,6 @@ def write(self, s): self.echo(s) self.s_hist.append(s.rstrip()) - def show_list(self, items, arg_pos, topline=None, formatter=None, current_item=None): shared = Struct() @@ -1345,7 +1346,7 @@ def lsize(): self.list_win.resize(rows + 2, w) else: docstring = self.format_docstring(self.docstring, max_w - 2, - max_h - height_offset) + max_h - height_offset) docstring_string = ''.join(docstring) rows += len(docstring) self.list_win.resize(rows, max_w) @@ -1445,7 +1446,7 @@ def tab(self, back=False): # 3. check to see if we can expand the current word if self.matches_iter.is_cseq(): - #TODO resolve this error-prone situation: + # TODO resolve this error-prone situation: # can't assign at same time to self.s and self.cursor_offset # because for cursor_offset # property to work correctly, self.s must already be set @@ -1458,7 +1459,7 @@ def tab(self, back=False): # 4. swap current word for a match list item elif self.matches_iter.matches: current_match = back and self.matches_iter.previous() \ - or next(self.matches_iter) + or next(self.matches_iter) try: self.show_list(self.matches_iter.matches, self.arg_pos, topline=self.funcprops, @@ -1500,7 +1501,7 @@ def send_current_line_to_editor(self): return '' self.f_string = '' - self.cpos = -1 # Set cursor position to -1 to prevent paren matching + self.cpos = -1 # Set cursor position to -1 to prevent paren matching self.iy, self.ix = self.scr.getyx() self.evaluating = True @@ -1531,6 +1532,7 @@ def send_current_line_to_editor(self): self.scr.redrawwin() return '' + class Statusbar(object): """This class provides the status bar at the bottom of the screen. It has message() and prompt() methods for user interactivity, as @@ -1687,7 +1689,7 @@ def clear(self): def init_wins(scr, config): """Initialise the two windows (the main repl interface and the little status bar at the bottom with some stuff in it)""" - #TODO: Document better what stuff is on the status bar. + # TODO: Document better what stuff is on the status bar. background = get_colpair(config, 'background') h, w = gethw() @@ -1719,11 +1721,13 @@ def sigwinch(unused_scr): global DO_RESIZE DO_RESIZE = True + def sigcont(unused_scr): sigwinch(unused_scr) # Forces the redraw curses.ungetch('\x00') + def gethw(): """I found this code on a usenet post, and snipped out the bit I needed, so thanks to whoever wrote that, sorry I forgot your name, I'm sure you're @@ -1761,7 +1765,8 @@ def gethw(): sizex = right - left + 1 sizey = bottom - top + 1 else: - sizex, sizey = stdscr.getmaxyx()# can't determine actual size - return default values + # can't determine actual size - return default values + sizex, sizey = stdscr.getmaxyx() h, w = sizey, sizex return h, w @@ -1796,7 +1801,7 @@ def do_resize(caller): global DO_RESIZE h, w = gethw() if not h: - # Hopefully this shouldn't happen. :) + # Hopefully this shouldn't happen. :) return curses.endwin() @@ -1878,7 +1883,8 @@ def main_curses(scr, args, config, interactive=True, locals_=None, old_sigwinch_handler = signal.signal(signal.SIGWINCH, lambda *_: sigwinch(scr)) # redraw window after being suspended - old_sigcont_handler = signal.signal(signal.SIGCONT, lambda *_: sigcont(scr)) + old_sigcont_handler = signal.signal( + signal.SIGCONT, lambda *_: sigcont(scr)) stdscr = scr try: @@ -1951,7 +1957,6 @@ def main_curses(scr, args, config, interactive=True, locals_=None, def main(args=None, locals_=None, banner=None): translations.init() - config, options, exec_args = argsparse(args) # Save stdin, stdout and stderr for later restoration @@ -1975,6 +1980,7 @@ def main(args=None, locals_=None, banner=None): sys.stdout.flush() return repl.extract_exit_value(exit_value) + if __name__ == '__main__': sys.exit(main()) diff --git a/bpython/config.py b/bpython/config.py index a30e76c9b..a3cca3bc1 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -183,7 +183,8 @@ def get_key_no_doublebind(command): struct.hist_length = config.getint('general', 'hist_length') struct.hist_duplicates = config.getboolean('general', 'hist_duplicates') struct.flush_output = config.getboolean('general', 'flush_output') - struct.default_autoreload = config.getboolean('general', 'default_autoreload') + struct.default_autoreload = config.getboolean( + 'general', 'default_autoreload') struct.pastebin_key = get_key_no_doublebind('pastebin') struct.copy_clipboard_key = get_key_no_doublebind('copy_clipboard') diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 7af127d9f..792f20b1c 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -139,7 +139,7 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): help=_("log debug messages to bpython.log")), Option('--paste', '-p', action='store_true', help=_("start by pasting lines of a file into session")), - ])) + ])) if options.log is None: options.log = 0 logging_levels = [logging.ERROR, logging.INFO, logging.DEBUG] diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index b8dc1964c..5dbe76a6d 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -205,4 +205,5 @@ def reload(): for _ in find_all_modules(): pass + find_iterator = find_all_modules() diff --git a/bpython/line.py b/bpython/line.py index 3a6806647..6456073f0 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -15,8 +15,8 @@ LinePart = namedtuple('LinePart', ['start', 'stop', 'word']) current_word_re = LazyReCompile( - r'(?' % len(self.bpython_history) self.bpython_history.append((len(source), None, - source.splitlines(True), filename)) + source.splitlines(True), filename)) return filename def __getitem__(self, key): diff --git a/bpython/repl.py b/bpython/repl.py index 88a9b6e19..4439d71eb 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -61,6 +61,7 @@ class RuntimeTimer(object): """Calculate running time""" + def __init__(self): self.reset_timer() self.time = time.monotonic if hasattr(time, 'monotonic') else time.time @@ -153,7 +154,8 @@ def runsource(self, source, filename=None, symbol='single', if encode and filename is not None: # files have encoding comments or implicit encoding of ASCII if encode != 'auto': - raise ValueError("shouldn't add encoding line to file contents") + raise ValueError( + "shouldn't add encoding line to file contents") encode = False if encode and not py3 and isinstance(source, str): @@ -622,7 +624,7 @@ def get_args(self): try: fake_cursor = self.current_line.index(func) + len(func) f = simpleeval.evaluate_current_attribute( - fake_cursor, self.current_line, self.interp.locals) + fake_cursor, self.current_line, self.interp.locals) except simpleeval.EvaluationError: return False except Exception: diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index b4934fb66..51b50e7ac 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -197,7 +197,7 @@ def evaluate_current_expression(cursor_offset, line, namespace=None): temp_line = line[:cursor_offset] + 'xxx' + line[cursor_offset:] temp_cursor = cursor_offset + 3 temp_attribute = line_properties.current_expression_attribute( - temp_cursor, temp_line) + temp_cursor, temp_line) if temp_attribute is None: raise EvaluationError("No current attribute") attr_before_cursor = temp_line[temp_attribute.start:temp_cursor] @@ -218,7 +218,7 @@ def parse_trees(cursor_offset, line): if largest_ast is None: raise EvaluationError( - "Corresponding ASTs to right of cursor are invalid") + "Corresponding ASTs to right of cursor are invalid") try: return simple_eval(largest_ast, namespace) except ValueError: @@ -238,7 +238,7 @@ def evaluate_current_attribute(cursor_offset, line, namespace=None): return getattr(obj, attr.word) except AttributeError: raise EvaluationError( - "can't lookup attribute %s on %r" % (attr.word, obj)) + "can't lookup attribute %s on %r" % (attr.word, obj)) def safe_get_attribute(obj, attr): @@ -255,6 +255,8 @@ def safe_get_attribute(obj, attr): class _ClassWithSlots(object): __slots__ = ['a'] + + member_descriptor = type(_ClassWithSlots.a) diff --git a/bpython/urwid.py b/bpython/urwid.py index acb153a27..f671dce7d 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -73,15 +73,15 @@ COLORMAP = { 'k': 'black', - 'r': 'dark red', # or light red? - 'g': 'dark green', # or light green? + 'r': 'dark red', # or light red? + 'g': 'dark green', # or light green? 'y': 'yellow', - 'b': 'dark blue', # or light blue? - 'm': 'dark magenta', # or light magenta? - 'c': 'dark cyan', # or light cyan? + 'b': 'dark blue', # or light blue? + 'm': 'dark magenta', # or light magenta? + 'c': 'dark cyan', # or light cyan? 'w': 'white', 'd': 'default', - } +} try: @@ -104,7 +104,6 @@ def lineReceived(self, line): self.repl.main_loop.process_input(line) self.repl.main_loop.process_input(['enter']) - class EvalFactory(protocol.ServerFactory): def __init__(self, myrepl): @@ -171,8 +170,10 @@ def keypress(self, size, key): else: return urwid.Edit.keypress(self, size, key) + urwid.register_signal(StatusbarEdit, 'prompt_enter') + class Statusbar(object): """Statusbar object, ripped off from bpython.cli. @@ -261,7 +262,7 @@ def settext(self, s, permanent=False): self.text.set_text(('main', s)) if permanent: - self.s = s + self.s = s def clear(self): """Clear the status bar.""" @@ -273,6 +274,7 @@ def _on_prompt_enter(self, edit, new_text): self.settext(self.s) urwid.emit_signal(self, 'prompt_result', new_text) + urwid.register_signal(Statusbar, 'prompt_result') @@ -288,6 +290,7 @@ def decoding_input_filter(keys, raw): converted_keys.append(key) return converted_keys + def format_tokens(tokensource): for token, text in tokensource: if text == '\n': @@ -450,15 +453,18 @@ def mouse_event(self, *args): finally: self._bpy_may_move_cursor = False + class BPythonListBox(urwid.ListBox): """Like `urwid.ListBox`, except that it does not eat up and down keys. """ + def keypress(self, size, key): if key not in ["up", "down"]: return urwid.ListBox.keypress(self, size, key) return key + class Tooltip(urwid.BoxWidget): """Container inspired by Overlay to position our tooltip. @@ -540,6 +546,7 @@ def render(self, size, focus=False): canvas.cursor = cursor return canvas + class URWIDInteraction(repl.Interaction): def __init__(self, config, statusbar, frame): repl.Interaction.__init__(self, config, statusbar) @@ -582,7 +589,7 @@ def _prompt_result(self, text): class URWIDRepl(repl.Repl): - _time_between_redraws = .05 # seconds + _time_between_redraws = .05 # seconds def __init__(self, event_loop, palette, interpreter, config): repl.Repl.__init__(self, interpreter, config) @@ -613,12 +620,13 @@ def __init__(self, event_loop, palette, interpreter, config): # String is straight from bpython.cli self.statusbar = Statusbar(config, - _(" <%s> Rewind <%s> Save <%s> Pastebin " - " <%s> Pager <%s> Show Source ") % - (config.undo_key, config.save_key, config.pastebin_key, - config.last_output_key, config.show_source_key), self.main_loop) + _(" <%s> Rewind <%s> Save <%s> Pastebin " + " <%s> Pager <%s> Show Source ") % + (config.undo_key, config.save_key, config.pastebin_key, + config.last_output_key, config.show_source_key), self.main_loop) self.frame.set_footer(self.statusbar.widget) - self.interact = URWIDInteraction(self.config, self.statusbar, self.frame) + self.interact = URWIDInteraction( + self.config, self.statusbar, self.frame) self.edits = [] self.edit = None @@ -692,10 +700,11 @@ def _get_current_line(self): if self.edit is None: return '' return self.edit.get_edit_text() + def _set_current_line(self, line): self.edit.set_edit_text(line) current_line = property(_get_current_line, _set_current_line, None, - "Return the current line (the one the cursor is in).") + "Return the current line (the one the cursor is in).") def cw(self): """Return the current word (incomplete word left of cursor).""" @@ -710,7 +719,7 @@ def cw(self): # Stolen from cli. TODO: clean up and split out. if (not text or - (not text[-1].isalnum() and text[-1] not in ('.', '_'))): + (not text[-1].isalnum() and text[-1] not in ('.', '_'))): return # Seek backwards in text for the first non-identifier char: @@ -731,10 +740,11 @@ def cpos(self): def _get_cursor_offset(self): return self.edit.edit_pos + def _set_cursor_offset(self, offset): self.edit.edit_pos = offset cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, - "The cursor offset from the beginning of the line") + "The cursor offset from the beginning of the line") def _populate_completion(self): widget_list = self.tooltip.body @@ -877,7 +887,8 @@ def reevaluate(self): if py3: self.stdout_hist += line + '\n' else: - self.stdout_hist += line.encode(locale.getpreferredencoding()) + '\n' + self.stdout_hist += line.encode( + locale.getpreferredencoding()) + '\n' self.print_line(line) self.s_hist[-1] += self.f_string # I decided it was easier to just do this manually @@ -898,7 +909,7 @@ def reevaluate(self): self.evaluating = False #map(self.push, self.history) - #^-- That's how simple this method was at first :( + # ^-- That's how simple this method was at first :( def write(self, s): """For overriding stdout defaults""" @@ -922,7 +933,6 @@ def write(self, s): self.echo(s) self.s_hist.append(s.rstrip()) - def push(self, s, insert_into_history=True): # Restore the original SIGINT handler. This is needed to be able # to break out of infinite loops. If the interpreter itself @@ -1061,7 +1071,7 @@ def handle_input(self, event): self.tab() elif urwid.command_map[event] == 'prev selectable': self.tab(True) - #else: + # else: # self.echo(repr(event)) def tab(self, back=False): @@ -1116,25 +1126,26 @@ def tab(self, back=False): finally: self._completion_update_suppressed = False + def main(args=None, locals_=None, banner=None): translations.init() # TODO: maybe support displays other than raw_display? config, options, exec_args = bpargs.parse(args, ( - 'Urwid options', None, [ - Option('--twisted', '-T', action='store_true', - help=_('Run twisted reactor.')), - Option('--reactor', '-r', - help=_('Select specific reactor (see --help-reactors). ' - 'Implies --twisted.')), - Option('--help-reactors', action='store_true', - help=_('List available reactors for -r.')), - Option('--plugin', '-p', - help=_('twistd plugin to run (use twistd for a list). ' - 'Use "--" to pass further options to the plugin.')), - Option('--server', '-s', type='int', - help=_('Port to run an eval server on (forces Twisted).')), - ])) + 'Urwid options', None, [ + Option('--twisted', '-T', action='store_true', + help=_('Run twisted reactor.')), + Option('--reactor', '-r', + help=_('Select specific reactor (see --help-reactors). ' + 'Implies --twisted.')), + Option('--help-reactors', action='store_true', + help=_('List available reactors for -r.')), + Option('--plugin', '-p', + help=_('twistd plugin to run (use twistd for a list). ' + 'Use "--" to pass further options to the plugin.')), + Option('--server', '-s', type='int', + help=_('Port to run an eval server on (forces Twisted).')), + ])) if options.help_reactors: try: @@ -1144,7 +1155,7 @@ def main(args=None, locals_=None, banner=None): print(' %-4s\t%s' % (r.shortName, r.description)) except ImportError: sys.stderr.write('No reactors are available. Please install ' - 'twisted for reactor support.\n') + 'twisted for reactor support.\n') return palette = [ @@ -1152,8 +1163,8 @@ def main(args=None, locals_=None, banner=None): 'bold' if color.isupper() else 'default') for name, color in iteritems(config.color_scheme)] palette.extend([ - ('bold ' + name, color + ',bold', background, monochrome) - for name, color, background, monochrome in palette]) + ('bold ' + name, color + ',bold', background, monochrome) + for name, color, background, monochrome in palette]) if options.server or options.plugin: options.twisted = True @@ -1163,7 +1174,7 @@ def main(args=None, locals_=None, banner=None): from twisted.application import reactors except ImportError: sys.stderr.write('No reactors are available. Please install ' - 'twisted for reactor support.\n') + 'twisted for reactor support.\n') return try: # XXX why does this not just return the reactor it installed? @@ -1172,7 +1183,7 @@ def main(args=None, locals_=None, banner=None): from twisted.internet import reactor except reactors.NoSuchReactor: sys.stderr.write('Reactor %s does not exist\n' % ( - options.reactor,)) + options.reactor,)) return event_loop = TwistedEventLoop(reactor) elif options.twisted: @@ -1180,7 +1191,7 @@ def main(args=None, locals_=None, banner=None): from twisted.internet import reactor except ImportError: sys.stderr.write('No reactors are available. Please install ' - 'twisted for reactor support.\n') + 'twisted for reactor support.\n') return event_loop = TwistedEventLoop(reactor) else: @@ -1196,7 +1207,7 @@ def main(args=None, locals_=None, banner=None): from twisted.application import service except ImportError: sys.stderr.write('No twisted plugins are available. Please install ' - 'twisted for twisted plugin support.\n') + 'twisted for twisted plugin support.\n') return for plug in plugin.getPlugins(service.IServiceMaker): @@ -1240,6 +1251,7 @@ def sigint(*args): # are called before we get around to starting the mainloop # (urwid raises an exception if we try to draw to the screen # before starting it). + def run_with_screen_before_mainloop(): try: # Currently we just set this to None because I do not @@ -1252,7 +1264,7 @@ def run_with_screen_before_mainloop(): # cannot re-enter the reactor. If using urwid's own # mainloop we *might* be able to do something similar and # re-enter its mainloop. - sys.stdin = None #FakeStdin(myrepl) + sys.stdin = None # FakeStdin(myrepl) sys.stdout = myrepl sys.stderr = myrepl @@ -1317,6 +1329,7 @@ def run_find_coroutine(): sys.stdout.flush() return repl.extract_exit_value(myrepl.exit_value) + def load_urwid_command_map(config): urwid.command_map[key_dispatch[config.up_one_line_key]] = 'cursor up' urwid.command_map[key_dispatch[config.down_one_line_key]] = 'cursor down' @@ -1329,6 +1342,7 @@ def load_urwid_command_map(config): urwid.command_map[key_dispatch[config.clear_word_key]] = 'clear word' urwid.command_map[key_dispatch[config.clear_line_key]] = 'clear line' + """ 'clear_screen': 'C-l', 'cut_to_buffer': 'C-k', From ae4a502a443e024bd82ed1a7b88adf8be2068a2c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 22 May 2018 17:18:23 +0200 Subject: [PATCH 0883/1650] Test with Python 3.8 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index be708063a..a7e0c7270 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ python: - "3.5" - "3.6" - "3.7-dev" + - "3.8-dev" - "pypy" - "pypy3" @@ -20,6 +21,7 @@ env: matrix: allow_failures: - python: "3.7-dev" + - python: "3.8-dev" - python: "pypy" - python: "pypy3" From 4e3673cc90348ed9e9d9c51b5773e0c307c75472 Mon Sep 17 00:00:00 2001 From: Greg Burek Date: Thu, 27 Sep 2018 10:41:03 -0700 Subject: [PATCH 0884/1650] Fix bpython-curses bug when passed an argument Currently, running `bpython-curses -i file.py` results in: ``` ... File ".local/share/virtualenvs/flask-ex-03Wix3mp/lib/python3.6/site-packages/bpython/cli.py", line 1905, in main_curses bpython.args.exec_code(interpreter, args) NameError: name 'bpython' is not defined ``` This PR addresses this error by correctly importing args and using it, like the curtsies implementation. --- bpython/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index 2a134d95b..5cd24261e 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -81,6 +81,7 @@ from .translations import _ from . import repl +from . import args as bpargs from ._py3compat import py3 from .pager import page from .args import parse as argsparse @@ -1914,7 +1915,7 @@ def main_curses(scr, args, config, interactive=True, locals_=None, if args: exit_value = () try: - bpython.args.exec_code(interpreter, args) + bpargs.exec_code(interpreter, args) except SystemExit as e: # The documentation of code.InteractiveInterpreter.runcode claims # that it reraises SystemExit. However, I can't manage to trigger From b214afaa44f3c7cffcc5da6b219fa1841963f152 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 27 Sep 2018 20:13:37 +0200 Subject: [PATCH 0885/1650] Switch to 3.7 --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a7e0c7270..2aad2fcce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: - "3.4" - "3.5" - "3.6" - - "3.7-dev" + - "3.7" - "3.8-dev" - "pypy" - "pypy3" @@ -20,7 +20,6 @@ env: matrix: allow_failures: - - python: "3.7-dev" - python: "3.8-dev" - python: "pypy" - python: "pypy3" From 94cfb15f55144320ebae64c50dacee5512722f43 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 27 Sep 2018 21:44:41 +0200 Subject: [PATCH 0886/1650] Fix test with PyPy 3 --- bpython/test/test_interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 48cb8ad55..d25d9d7d2 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -67,7 +67,7 @@ def gfunc(): i.runsource('gfunc()') - if pypy: + if pypy and not py3: global_not_found = "global name 'gfunc' is not defined" else: global_not_found = "name 'gfunc' is not defined" From 725c9b78c3b41a72e75b8e22e10d51e74a59c15d Mon Sep 17 00:00:00 2001 From: Attila Szollosi Date: Thu, 4 Oct 2018 13:37:58 +0200 Subject: [PATCH 0887/1650] Revert "Switch to 3.7" This reverts commit b214afaa44f3c7cffcc5da6b219fa1841963f152. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2aad2fcce..a7e0c7270 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: - "3.4" - "3.5" - "3.6" - - "3.7" + - "3.7-dev" - "3.8-dev" - "pypy" - "pypy3" @@ -20,6 +20,7 @@ env: matrix: allow_failures: + - python: "3.7-dev" - python: "3.8-dev" - python: "pypy" - python: "pypy3" From 9747551e63250d84b52e4144348b265fc984bbe3 Mon Sep 17 00:00:00 2001 From: Attila Szollosi Date: Thu, 4 Oct 2018 09:39:37 +0200 Subject: [PATCH 0888/1650] Merge send_to_stdout and send_to_stderr; fix newline handling (#744) --- bpython/curtsiesfrontend/repl.py | 51 ++++++------------------------ bpython/test/test_curtsies_repl.py | 28 ++++++++++++++++ 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c4614a580..d36997a22 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -229,7 +229,7 @@ def readline(self): value = self.readline_results.pop(0) else: value = 'no saved input available' - self.repl.send_to_stdout(value) + self.repl.send_to_stdouterr(value) return value @@ -333,7 +333,7 @@ def __init__(self, if interp is None: interp = Interp(locals=locals_) - interp.write = self.send_to_stderr + interp.write = self.send_to_stdouterr if banner is None: if config.help_key: banner = (_('Welcome to bpython!') + ' ' + @@ -393,9 +393,9 @@ def __init__(self, # filenos match the backing device for libs that expect it, # but writing to them will do weird things to the display - self.stdout = FakeOutput(self.coderunner, self.send_to_stdout, + self.stdout = FakeOutput(self.coderunner, self.send_to_stdouterr, fileno=sys.__stdout__.fileno()) - self.stderr = FakeOutput(self.coderunner, self.send_to_stderr, + self.stderr = FakeOutput(self.coderunner, self.send_to_stdouterr, fileno=sys.__stderr__.fileno()) self.stdin = FakeStdin(self.coderunner, self, self.edit_keys) @@ -1140,27 +1140,16 @@ def clear_current_block(self, remove_from_history=True): def get_current_block(self): return '\n'.join(self.buffer + [self.current_line]) - def move_current_stdouterr_line_up(self): - """Append self.current_stdouterr_line to self.display_lines - then clean it.""" - self.display_lines.extend(paint.display_linize( - self.current_stdouterr_line, self.width)) - self.current_stdouterr_line = '' + def send_to_stdouterr(self, output): + """Send unicode strings or FmtStr to Repl stdout or stderr - def send_to_stdout(self, output): - """Send unicode string to Repl stdout""" + Must be able to handle FmtStrs because interpreter pass in + tracebacks already formatted.""" if not output: return lines = output.split('\n') - if all(not line for line in lines): - # If the string consist only of newline characters, - # str.split returns one more empty strings. - lines = lines[:-1] logger.debug('display_lines: %r', self.display_lines) - if lines[0]: - self.current_stdouterr_line += lines[0] - else: - self.move_current_stdouterr_line_up() + self.current_stdouterr_line += lines[0] if len(lines) > 1: self.display_lines.extend(paint.display_linize( self.current_stdouterr_line, self.width, blank_line=True)) @@ -1171,26 +1160,6 @@ def send_to_stdout(self, output): self.current_stdouterr_line = lines[-1] logger.debug('display_lines: %r', self.display_lines) - def send_to_stderr(self, error): - """Send unicode strings or FmtStr to Repl stderr - - Must be able to handle FmtStrs because interpreter pass in - tracebacks already formatted.""" - if not error: - return - lines = error.split('\n') - if all(not line for line in lines): - # If the string consist only of newline characters, - # str.split returns one more empty strings. - lines = lines[:-1] - if lines[-1]: - self.current_stdouterr_line += lines[-1] - else: - self.move_current_stdouterr_line_up() - self.display_lines.extend(sum((paint.display_linize(line, self.width, - blank_line=True) - for line in lines[:-1]), [])) - def send_to_stdin(self, line): if line.endswith('\n'): self.display_lines.extend( @@ -1653,7 +1622,7 @@ def reevaluate(self, insert_into_history=False): if not self.weak_rewind: self.interp = self.interp.__class__() - self.interp.write = self.send_to_stderr + self.interp.write = self.send_to_stdouterr self.coderunner.interp = self.interp self.initialize_interp() diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index e91fff022..330ed02d2 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -264,6 +264,34 @@ def test_interactive(self): self.assertEqual(out.getvalue(), '0.5\n0.5\n') +class TestStdOutErr(TestCase): + def setUp(self): + self.repl = create_repl() + + def test_newline(self): + self.repl.send_to_stdouterr('\n\n') + self.assertEqual(self.repl.display_lines[-2], '') + self.assertEqual(self.repl.display_lines[-1], '') + self.assertEqual(self.repl.current_stdouterr_line, '') + + def test_leading_newline(self): + self.repl.send_to_stdouterr('\nfoo\n') + self.assertEqual(self.repl.display_lines[-2], '') + self.assertEqual(self.repl.display_lines[-1], 'foo') + self.assertEqual(self.repl.current_stdouterr_line, '') + + def test_no_trailing_newline(self): + self.repl.send_to_stdouterr('foo') + self.assertEqual(self.repl.current_stdouterr_line, 'foo') + + def test_print_without_newline_then_print_with_leading_newline(self): + self.repl.send_to_stdouterr('foo') + self.repl.send_to_stdouterr('\nbar\n') + self.assertEqual(self.repl.display_lines[-2], 'foo') + self.assertEqual(self.repl.display_lines[-1], 'bar') + self.assertEqual(self.repl.current_stdouterr_line, '') + + class TestPredictedIndent(TestCase): def setUp(self): self.repl = create_repl() From f1acdc13550d2c569288e3a8444da587fbd7ac0e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 5 Oct 2018 13:38:45 +0200 Subject: [PATCH 0889/1650] Update changelog --- CHANGELOG | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 44db01884..cca433008 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,11 +11,11 @@ New features: Fixes: * Fix file locking on Windows. * Exit gracefully if config file fails to be loaded due to encoding errors. +* #744: Fix newline handling. + Thanks to Attila Szöllősi. Support for Python 3.3 has been dropped. -Fixes: - 0.17.1 ------ @@ -43,7 +43,7 @@ Fixes: * #654: Do not modify history file during tests. * #658: Fix newline handling. Thanks to Attila Szöllősi. -* #670: Fix handlign of ANSI escape codes. +* #670: Fix handling of ANSI escape codes. Thanks to Attila Szöllősi. * #687: Fix encoding of jedi completions. @@ -60,8 +60,8 @@ Fixes: Thanks to Aditya Gupta. * #614: Fix issues when view source. Thanks to Daniel Hahler. -* #625: Fix issues when runnings scripts with non-ASCII characters. -* #639: Fix compatbility issues with pdb++. +* #625: Fix issues when running scripts with non-ASCII characters. +* #639: Fix compatibility issues with pdb++. Thanks to Daniel Hahler. Support for Python 2.6 has been dropped. From f363204db91d793f7953aaf1ba728dfa7556f832 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Nov 2018 20:26:27 +0100 Subject: [PATCH 0890/1650] Enable 3.7 again since it got released --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a7e0c7270..a6e6bfbde 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: - "3.4" - "3.5" - "3.6" - - "3.7-dev" + - "3.7" - "3.8-dev" - "pypy" - "pypy3" @@ -20,7 +20,7 @@ env: matrix: allow_failures: - - python: "3.7-dev" + - python: "3.7" - python: "3.8-dev" - python: "pypy" - python: "pypy3" From 167c5621b77033dd10d8c17ee40b8c34d4188d61 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Nov 2018 20:50:34 +0100 Subject: [PATCH 0891/1650] travis: Use xenial --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a6e6bfbde..d92366bea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: python sudo: false +dist: xenial notifications: webhooks: - secure: "QXcEHVnOi5mZpONkHSu1tydj8EK3G7xJ7Wv/WYhJ5soNUpEJgi6YwR1WcxSjo7qyi8hTL+4jc+ID0TpKDeS1lpXF41kG9xf5kdxw5OL0EnMkrP9okUN0Ip8taEhd8w+6+dGmfZrx2nXOg1kBU7W5cE90XYqEtNDVXXgNeilT+ik=" From 1d198efc01431e41618b056aa35c783e6f4f3693 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Nov 2018 20:58:43 +0100 Subject: [PATCH 0892/1650] travis: remove 3.7 from allowed_failures --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d92366bea..2ae9ef4a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,6 @@ env: matrix: allow_failures: - - python: "3.7" - python: "3.8-dev" - python: "pypy" - python: "pypy3" From ac473c3c9f15d04c897b82b480a2d1b78a97e916 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Dec 2018 00:47:43 +0100 Subject: [PATCH 0893/1650] Remove unnecessary import --- bpython/urwid.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index f671dce7d..8d575dc61 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -141,7 +141,6 @@ def wrapper(*args, **kwargs): except: # This is the same as in urwid. # We are obviously not supposed to ever hit this. - import sys print(sys.exc_info()) self._exc_info = sys.exc_info() self.reactor.crash() From 146be274410efa6fd3b8d1807d9dcea20befe309 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Dec 2018 00:49:36 +0100 Subject: [PATCH 0894/1650] Avoid unsafe default argument --- bpython/test/test_repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 67a7d37a2..532ee5865 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -22,7 +22,7 @@ def setup_config(conf): config_struct = config.Struct() config.loadini(config_struct, TEST_CONFIG) - if 'autocomplete_mode' in conf: + if conf is not None and 'autocomplete_mode' in conf: config_struct.autocomplete_mode = conf['autocomplete_mode'] return config_struct @@ -37,7 +37,7 @@ def reset(self): class FakeRepl(repl.Repl): - def __init__(self, conf={}): + def __init__(self, conf=None): repl.Repl.__init__(self, repl.Interpreter(), setup_config(conf)) self.current_line = "" self.cursor_offset = 0 From d3a33e77d9197282542ea5462f27078f6c218c6b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Dec 2018 00:51:31 +0100 Subject: [PATCH 0895/1650] Avoid unsafe default argument --- bpython/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/__init__.py b/bpython/__init__.py index a4f3bcc37..779a1a682 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -33,6 +33,9 @@ package_dir = os.path.abspath(os.path.dirname(__file__)) -def embed(locals_=None, args=['-i', '-q'], banner=None): +def embed(locals_=None, args=None, banner=None): + if args is None: + args = ['-i', '-q'] + from .curtsies import main return main(args, locals_, banner) From 8e6f8f78e1840111f789cd8691bd45f9570a0358 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Dec 2018 00:52:05 +0100 Subject: [PATCH 0896/1650] Remove unused import --- bpython/repl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 4439d71eb..cf87ad097 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -31,7 +31,6 @@ import os import pkgutil import pydoc -import re import shlex import subprocess import sys From 1660fa5301f406c2c7c479c2332cf68b70dd763c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Dec 2018 01:05:40 +0100 Subject: [PATCH 0897/1650] Update naming --- bpython/curtsiesfrontend/replpainter.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 36c9c1324..46c07458b 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -56,7 +56,7 @@ def paginate(rows, matches, current, words_wide): return matches[per_page * current_page:per_page * (current_page + 1)] -def matches_lines(rows, columns, matches, current, config, format): +def matches_lines(rows, columns, matches, current, config, match_format): highlight_color = func_for_letter(config.color_scheme['operator'].lower()) if not matches: @@ -64,22 +64,22 @@ def matches_lines(rows, columns, matches, current, config, format): color = func_for_letter(config.color_scheme['main']) max_match_width = max(len(m) for m in matches) words_wide = max(1, (columns - 1) // (max_match_width + 1)) - matches = [format(m) for m in matches] + matches = [match_format(m) for m in matches] if current: - current = format(current) + current = match_format(current) matches = paginate(rows, matches, current, words_wide) - matches_lines = [fmtstr(' ').join(color(m.ljust(max_match_width)) - if m != current - else highlight_color( - m.ljust(max_match_width)) - for m in matches[i:i + words_wide]) + result = [fmtstr(' ').join(color(m.ljust(max_match_width)) + if m != current + else highlight_color( + m.ljust(max_match_width)) + for m in matches[i:i + words_wide]) for i in range(0, len(matches), words_wide)] logger.debug('match: %r' % current) - logger.debug('matches_lines: %r' % matches_lines) - return matches_lines + logger.debug('matches_lines: %r' % result) + return result def formatted_argspec(funcprops, arg_pos, columns, config): @@ -175,7 +175,7 @@ def formatted_docstring(docstring, columns, config): def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, - config, format): + config, match_format): """Returns painted completions, funcprops, match, docstring etc.""" if not (rows and columns): return fsarray(0, 0) @@ -185,7 +185,7 @@ def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, from_doc = (formatted_docstring(docstring, width, config) if docstring else []) from_matches = (matches_lines(max(1, rows - len(from_argspec) - 2), - width, matches, match, config, format) + width, matches, match, config, match_format) if matches else []) lines = from_argspec + from_matches + from_doc From 3bd1c256525775d469653c12ae3cdd0d6f6a600d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Dec 2018 01:07:05 +0100 Subject: [PATCH 0898/1650] Remove unused function --- bpython/history.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bpython/history.py b/bpython/history.py index bd9301b03..ae9c31628 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -165,12 +165,6 @@ def enter(self, line): if self.index == 0: self.saved_line = line - @classmethod - def from_filename(cls, filename): - history = cls() - history.load(filename) - return history - def reset(self): self.index = 0 self.saved_line = '' From 68df142edb1f9adb0b348374277211e1802de39c Mon Sep 17 00:00:00 2001 From: benkrig Date: Wed, 9 Jan 2019 19:41:03 -0800 Subject: [PATCH 0899/1650] fix exit code issue --- bpython/curtsiesfrontend/coderunner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 647a7dca1..f9cd19589 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -183,7 +183,7 @@ def _blocking_run_code(self): try: unfinished = self.interp.runsource(self.source) except SystemExit as e: - return SystemExitRequest(e.args) + return SystemExitRequest(*e.args) return Unfinished() if unfinished else Done() def request_from_main_context(self, force_refresh=False): From 42ae73040ec6faa7b82d487f53d34132588ee6e7 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 3 Mar 2019 15:54:01 -0800 Subject: [PATCH 0900/1650] Fix 767 by using correct function --- bpython/curtsiesfrontend/replpainter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 46c07458b..5dd7d92ea 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -5,7 +5,7 @@ import itertools from six.moves import range -from curtsies import fsarray, fmtstr +from curtsies import fsarray, fmtstr, FSArray from curtsies.formatstring import linesplit from curtsies.fmtfuncs import bold @@ -178,7 +178,7 @@ def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, config, match_format): """Returns painted completions, funcprops, match, docstring etc.""" if not (rows and columns): - return fsarray(0, 0) + return FSArray(0, 0) width = columns - 4 from_argspec = (formatted_argspec(funcprops, arg_pos, width, config) if funcprops else []) From 3f1caba7faf4efab7a4157b22f9b6ea632099564 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 2 Apr 2019 21:46:42 +0200 Subject: [PATCH 0901/1650] Update changelog --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index cca433008..92e913568 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,9 @@ Fixes: * Exit gracefully if config file fails to be loaded due to encoding errors. * #744: Fix newline handling. Thanks to Attila Szöllősi. +* #731: Fix exit code. + Thanks to benkrig. +* #767: Fix crash when matching certain lines in history. Support for Python 3.3 has been dropped. From bcf70785dd90a03b8db6eca26d789d7ed629752d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 2 Apr 2019 21:53:14 +0200 Subject: [PATCH 0902/1650] Start development of 0.19 --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 92e913568..6be4f3959 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,13 @@ Changelog ========= +0.19 +---- + +New features: + +Fixes: + 0.18 ---- From 286fd3612b1c6104ff24fa84ebb045684bf5c57b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 2 Apr 2019 21:53:32 +0200 Subject: [PATCH 0903/1650] Point back to master --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 961e55c39..ed68c8cbc 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ |ImageLink|_ -.. |ImageLink| image:: https://travis-ci.org/bpython/bpython.svg?branch=bugfix-0.17 +.. |ImageLink| image:: https://travis-ci.org/bpython/bpython.svg?branch=master .. _ImageLink: https://travis-ci.org/bpython/bpython *********************************************************************** From a9407b643ceb4268611fd31cd83b2d0b57df0a28 Mon Sep 17 00:00:00 2001 From: Benedikt Rascher-Friesenhausen Date: Sat, 16 Feb 2019 11:07:51 +0100 Subject: [PATCH 0904/1650] Display the correct signature for a decorated function in Python 3 This patch changes it so that `inspect.signature` is used instead of `inspect.getfullargspec` to get a function's signature, when using Python 3. Python 3.3 introduced the `inspect.signature` function as a new way to get the signature of a function (as an alternative to `inspect.getargspec` and `inspect.getfullargspec`). `inspect.signature` has the advantage that it preserves the signature of a decorated function if `functools.wraps` is used to decorated the wrapper function. Having a function's signature available is very hepful, especially when testing things out in a REPL. --- bpython/inspection.py | 54 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 10b7db761..5817fac08 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -250,11 +250,10 @@ def getfuncprops(func, f): return None try: if py3: - argspec = inspect.getfullargspec(f) + argspec = get_argspec_from_signature(f) else: - argspec = inspect.getargspec(f) + argspec = list(inspect.getargspec(f)) - argspec = list(argspec) fixlongargs(f, argspec) if len(argspec) == 4: argspec = argspec + [list(), dict(), None] @@ -284,6 +283,55 @@ def is_callable(obj): return callable(obj) +def get_argspec_from_signature(f): + """Get callable signature from inspect.signature in argspec format. + + inspect.signature is a Python 3 only function that returns the signature of + a function. Its advantage over inspect.getfullargspec is that it returns + the signature of a decorated function, if the wrapper function itself is + decorated with functools.wraps. + + """ + args = [] + varargs = varkwargs = None + defaults = [] + kwonly = [] + kwonly_defaults = {} + annotations = {} + + signature = inspect.signature(f) + for parameter in signature.parameters.values(): + if parameter.annotation is not inspect._empty: + annotations[parameter.name] = parameter.annotation + + if parameter.kind == inspect._ParameterKind.POSITIONAL_OR_KEYWORD: + args.append(parameter.name) + if parameter.default is not inspect._empty: + defaults.append(parameter.default) + elif parameter.kind == inspect._ParameterKind.POSITIONAL_ONLY: + args.append(parameter.name) + elif parameter.kind == inspect._ParameterKind.VAR_POSITIONAL: + varargs = parameter.name + elif parameter.kind == inspect._ParameterKind.KEYWORD_ONLY: + kwonly.append(parameter.name) + kwonly_defaults[parameter.name] = parameter.default + elif parameter.kind == inspect._ParameterKind.VAR_KEYWORD: + varkwargs = parameter.name + + # inspect.getfullargspec returns None for 'defaults', 'kwonly_defaults' and + # 'annotations' if there are no values for them. + if not defaults: + defaults = None + + if not kwonly_defaults: + kwonly_defaults = None + + if not annotations: + annotations = None + + return [args, varargs, varkwargs, defaults, kwonly, kwonly_defaults, annotations] + + get_encoding_line_re = LazyReCompile(r'^.*coding[:=]\s*([-\w.]+).*$') From 0a5bd6e84e10bb414db2ca23854432050a6f630b Mon Sep 17 00:00:00 2001 From: Benedikt Rascher-Friesenhausen Date: Sat, 16 Feb 2019 11:45:37 +0100 Subject: [PATCH 0905/1650] Correctly reference inspect parameter kinds Instead of referencing parameter kinds as class attributes on the private `_ParameterKind` class we reference them on the public `Parameter` class. This has two advantages: 1) We don't use a private interface. 2) The class attributes on `_ParameterKind` have only been added in Python 3.5, but on `Parameter` they have existed since Python 3.3. --- bpython/inspection.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 5817fac08..410ac3173 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -304,18 +304,18 @@ def get_argspec_from_signature(f): if parameter.annotation is not inspect._empty: annotations[parameter.name] = parameter.annotation - if parameter.kind == inspect._ParameterKind.POSITIONAL_OR_KEYWORD: + if parameter.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD: args.append(parameter.name) if parameter.default is not inspect._empty: defaults.append(parameter.default) - elif parameter.kind == inspect._ParameterKind.POSITIONAL_ONLY: + elif parameter.kind == inspect.Parameter.POSITIONAL_ONLY: args.append(parameter.name) - elif parameter.kind == inspect._ParameterKind.VAR_POSITIONAL: + elif parameter.kind == inspect.Parameter.VAR_POSITIONAL: varargs = parameter.name - elif parameter.kind == inspect._ParameterKind.KEYWORD_ONLY: + elif parameter.kind == inspect.Parameter.KEYWORD_ONLY: kwonly.append(parameter.name) kwonly_defaults[parameter.name] = parameter.default - elif parameter.kind == inspect._ParameterKind.VAR_KEYWORD: + elif parameter.kind == inspect.Parameter.VAR_KEYWORD: varkwargs = parameter.name # inspect.getfullargspec returns None for 'defaults', 'kwonly_defaults' and From cd6886a5b1ea8651bc9dee686fb82282a66c41d3 Mon Sep 17 00:00:00 2001 From: Benedikt Rascher-Friesenhausen Date: Sat, 16 Feb 2019 11:50:02 +0100 Subject: [PATCH 0906/1650] Catch function signature inspection errors for built-in types Some built-in functions (e.g. `map`) can't be inspected with `inspect.getargspec`, `inspect.getfullargspec` or `inspect.signature`. The exceptions from `inspect.getargspec` and `inspect.getfullargspec` are all caught in the code, but `inspect.signature` raises a `ValueError` instead of a `TypeError`. This exception is now also caught. --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 410ac3173..49423e45a 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -259,7 +259,7 @@ def getfuncprops(func, f): argspec = argspec + [list(), dict(), None] argspec = ArgSpec(*argspec) fprops = FuncProps(func, argspec, is_bound_method) - except (TypeError, KeyError): + except (TypeError, KeyError, ValueError): with AttrCleaner(f): argspec = getpydocspec(f, func) if argspec is None: From 06382d96c314a9fc8f02e9d7830a5b5c165ef8f1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 2 Apr 2019 22:24:03 +0200 Subject: [PATCH 0907/1650] Update changelog --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 6be4f3959..5e529171e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,8 @@ Changelog New features: Fixes: +* #765: Display correct signature for decorted functions. + Thanks to Bendeikt Rascher-Friesenhausen. 0.18 ---- From ade9c77625e9ee0d438087ced8940b173a17f17e Mon Sep 17 00:00:00 2001 From: Wis <~@wis.am> Date: Wed, 1 May 2019 01:48:01 +0300 Subject: [PATCH 0908/1650] 10x the too low hist_length default of 100 to 1000 I just lost a useful function I wrote that I hoped I could find in the history file. a utf-8 encoded history file having 1000 lines of code less that 80 columns/characters long less than ~80 kilobytes in size. or is the concern more about the look up time for autocomplete and the responsiveness of typing? --- bpython/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index a3cca3bc1..aca3c2f5b 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -77,7 +77,7 @@ def loadini(struct, configfile): 'highlight_show_source': True, 'hist_duplicates': True, 'hist_file': '~/.pythonhist', - 'hist_length': 100, + 'hist_length': 1000, 'paste_time': 0.02, 'pastebin_confirm': True, 'pastebin_expiry': '1week', From c6adc1bde38242d867476f58b4bd0771ddfbbb3b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 22 Sep 2019 22:18:23 +0200 Subject: [PATCH 0909/1650] Update translations --- bpython/translations/bpython.pot | 147 +++++++------- .../translations/de/LC_MESSAGES/bpython.po | 164 ++++++++-------- .../translations/es_ES/LC_MESSAGES/bpython.po | 145 +++++++------- .../translations/fr_FR/LC_MESSAGES/bpython.po | 180 ++++++++++-------- .../translations/it_IT/LC_MESSAGES/bpython.po | 145 +++++++------- .../translations/nl_NL/LC_MESSAGES/bpython.po | 145 +++++++------- 6 files changed, 508 insertions(+), 418 deletions(-) diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 074e4a475..00f5329a0 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -1,289 +1,302 @@ # Translations template for bpython. -# Copyright (C) 2015 ORGANIZATION +# Copyright (C) 2019 ORGANIZATION # This file is distributed under the same license as the bpython project. -# FIRST AUTHOR , 2015. +# FIRST AUTHOR , 2019. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.15.dev98\n" +"Project-Id-Version: bpython 0.19.dev6\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-03-24 00:25+0100\n" +"POT-Creation-Date: 2019-09-22 22:17+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 1.3\n" +"Generated-By: Babel 2.6.0\n" -#: bpython/args.py:59 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:69 +#: bpython/args.py:73 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:71 +#: bpython/args.py:75 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:74 +#: bpython/args.py:78 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:76 +#: bpython/args.py:80 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "y" msgstr "" -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "yes" msgstr "" -#: bpython/cli.py:1695 +#: bpython/cli.py:1705 msgid "Rewind" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1706 msgid "Save" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1707 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1708 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1709 msgid "Show Source" msgstr "" -#: bpython/curtsies.py:37 +#: bpython/curtsies.py:139 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:39 +#: bpython/curtsies.py:141 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:228 +#: bpython/history.py:222 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:94 +#: bpython/paste.py:96 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:96 +#: bpython/paste.py:98 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:100 +#: bpython/paste.py:102 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:103 +#: bpython/paste.py:105 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:109 +#: bpython/paste.py:111 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:549 +#: bpython/repl.py:672 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:554 +#: bpython/repl.py:677 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:559 +#: bpython/repl.py:682 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:561 +#: bpython/repl.py:684 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:694 +#: bpython/repl.py:815 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 +#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:709 +#: bpython/repl.py:830 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:713 +#: bpython/repl.py:834 msgid "overwrite" msgstr "" -#: bpython/repl.py:715 +#: bpython/repl.py:836 msgid "append" msgstr "" -#: bpython/repl.py:727 bpython/repl.py:1022 +#: bpython/repl.py:848 bpython/repl.py:1140 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:729 +#: bpython/repl.py:850 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:735 +#: bpython/repl.py:856 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:863 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:744 +#: bpython/repl.py:865 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:753 +#: bpython/repl.py:874 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:754 +#: bpython/repl.py:875 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:761 +#: bpython/repl.py:882 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:768 +#: bpython/repl.py:888 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:772 +#: bpython/repl.py:892 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:780 +#: bpython/repl.py:900 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:783 +#: bpython/repl.py:903 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:817 +#: bpython/repl.py:938 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:824 bpython/repl.py:828 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:831 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1007 +#: bpython/repl.py:1126 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1029 +#: bpython/repl.py:1148 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1032 -msgid "Error editing config file." +#: bpython/repl.py:1151 +#, python-format +msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:619 +#: bpython/urwid.py:622 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1130 +#: bpython/urwid.py:1138 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1141 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1135 +#: bpython/urwid.py:1143 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1146 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:344 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:340 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:565 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:582 +#: bpython/curtsiesfrontend/repl.py:660 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:855 +#: bpython/curtsiesfrontend/repl.py:929 +msgid "Session not reevaluated because it was not edited" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:941 +msgid "Session not reevaluated because saved file was blank" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:950 +msgid "Session edited and reevaluated" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:960 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:861 +#: bpython/curtsiesfrontend/repl.py:966 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:866 +#: bpython/curtsiesfrontend/repl.py:971 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:871 +#: bpython/curtsiesfrontend/repl.py:976 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 094fab1e5..41a63bdab 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,289 +7,301 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-03-24 00:25+0100\n" -"PO-Revision-Date: 2015-03-24 00:27+0100\n" +"POT-Creation-Date: 2019-09-22 22:17+0200\n" +"PO-Revision-Date: 2019-09-22 22:21+0200\n" "Last-Translator: Sebastian Ramacher \n" +"Language: de\n" "Language-Team: de \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 1.3\n" -"Language: de\n" -"X-Generator: Poedit 1.6.10\n" +"Generated-By: Babel 2.6.0\n" -#: bpython/args.py:59 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back to " -"the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back " +"to the regular Python interpreter." msgstr "" -#: bpython/args.py:69 +#: bpython/args.py:73 msgid "Use CONFIG instead of default config file." msgstr "Verwende CONFIG antatt der standardmäßigen Konfigurationsdatei." -#: bpython/args.py:71 +#: bpython/args.py:75 msgid "Drop to bpython shell after running file instead of exiting." msgstr "Verbleibe in bpython nach dem Ausführen der Datei." -#: bpython/args.py:74 +#: bpython/args.py:78 msgid "Don't flush the output to stdout." msgstr "Gib Ausgabe beim Beenden nicht ernaut auf stdout aus." -#: bpython/args.py:76 +#: bpython/args.py:80 msgid "Print version and exit." msgstr "Zeige Versionsinformationen an und beende." -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "y" msgstr "j" -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "yes" msgstr "ja" -#: bpython/cli.py:1695 +#: bpython/cli.py:1705 msgid "Rewind" msgstr "Rückgängig" -#: bpython/cli.py:1696 +#: bpython/cli.py:1706 msgid "Save" msgstr "Speichern" -#: bpython/cli.py:1697 +#: bpython/cli.py:1707 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1708 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1709 msgid "Show Source" msgstr "Quellcode anzeigen" -#: bpython/curtsies.py:37 +#: bpython/curtsies.py:139 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:39 +#: bpython/curtsies.py:141 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:228 +#: bpython/history.py:222 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" -#: bpython/paste.py:94 +#: bpython/paste.py:96 msgid "Helper program not found." msgstr "Hilfsprogramm konnte nicht gefunden werden." -#: bpython/paste.py:96 +#: bpython/paste.py:98 msgid "Helper program could not be run." msgstr "Hilfsprogramm konnte nicht ausgeführt werden." -#: bpython/paste.py:100 +#: bpython/paste.py:102 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "Hilfsprogramm beendete mit Status %d." -#: bpython/paste.py:103 +#: bpython/paste.py:105 msgid "No output from helper program." msgstr "Keine Ausgabe von Hilfsprogramm vorhanden." -#: bpython/paste.py:109 +#: bpython/paste.py:111 msgid "Failed to recognize the helper program's output as an URL." msgstr "Konnte Ausgabe von Hilfsprogramm nicht verarbeiten." -#: bpython/repl.py:549 +#: bpython/repl.py:672 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:554 +#: bpython/repl.py:677 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" -#: bpython/repl.py:559 +#: bpython/repl.py:682 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/repl.py:561 +#: bpython/repl.py:684 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" -#: bpython/repl.py:694 +#: bpython/repl.py:815 msgid "Save to file (Esc to cancel): " msgstr "In Datei speichern (Esc um abzubrechen): " -#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 +#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 msgid "Save cancelled." msgstr "Speichern abgebrochen." -#: bpython/repl.py:709 +#: bpython/repl.py:830 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " -msgstr "" -"%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" +msgstr "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" -#: bpython/repl.py:713 +#: bpython/repl.py:834 msgid "overwrite" msgstr "überschreiben" -#: bpython/repl.py:715 +#: bpython/repl.py:836 msgid "append" msgstr "anhängen" -#: bpython/repl.py:727 bpython/repl.py:1022 +#: bpython/repl.py:848 bpython/repl.py:1140 #, python-format msgid "Error writing file '%s': %s" msgstr "Fehler beim Schreiben in Datei '%s': %s" -#: bpython/repl.py:729 +#: bpython/repl.py:850 #, python-format msgid "Saved to %s." msgstr "Nach %s gespeichert." -#: bpython/repl.py:735 +#: bpython/repl.py:856 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/repl.py:742 +#: bpython/repl.py:863 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/repl.py:744 +#: bpython/repl.py:865 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:753 +#: bpython/repl.py:874 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:754 +#: bpython/repl.py:875 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:761 +#: bpython/repl.py:882 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:768 +#: bpython/repl.py:888 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." -#: bpython/repl.py:772 +#: bpython/repl.py:892 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:780 +#: bpython/repl.py:900 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:783 +#: bpython/repl.py:903 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:817 +#: bpython/repl.py:938 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:824 bpython/repl.py:828 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "Rückgängigmachen abgebrochen" -#: bpython/repl.py:831 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1007 +#: bpython/repl.py:1126 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt werden? " -"(j/N)" +"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " +"werden? (j/N)" -#: bpython/repl.py:1029 +#: bpython/repl.py:1148 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" "bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " "Änderungen übernommen werden." -#: bpython/repl.py:1032 -msgid "Error editing config file." -msgstr "Fehler beim Bearbeiten der Konfigurationsdatei." +#: bpython/repl.py:1151 +#, python-format +msgid "Error editing config file: %s" +msgstr "Fehler beim Bearbeiten der Konfigurationsdatei: %s" -#: bpython/urwid.py:619 +#: bpython/urwid.py:622 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1130 +#: bpython/urwid.py:1138 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1141 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1135 +#: bpython/urwid.py:1143 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1146 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:344 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:340 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#: bpython/curtsiesfrontend/repl.py:565 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" -#: bpython/curtsiesfrontend/repl.py:582 +#: bpython/curtsiesfrontend/repl.py:660 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:855 +#: bpython/curtsiesfrontend/repl.py:929 +msgid "Session not reevaluated because it was not edited" +msgstr "Die Sitzung wurde nicht neu ausgeführt, da sie nicht berabeitet wurde" + +#: bpython/curtsiesfrontend/repl.py:941 +msgid "Session not reevaluated because saved file was blank" +msgstr "Die Sitzung wurde nicht neu ausgeführt, da die gespeicherte Datei leer war" + +#: bpython/curtsiesfrontend/repl.py:950 +msgid "Session edited and reevaluated" +msgstr "Sitzung bearbeitet und neu ausgeführt" + +#: bpython/curtsiesfrontend/repl.py:960 #, python-format msgid "Reloaded at %s by user." -msgstr "" +msgstr "Bei %s vom Benutzer neu geladen." -#: bpython/curtsiesfrontend/repl.py:861 +#: bpython/curtsiesfrontend/repl.py:966 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:866 +#: bpython/curtsiesfrontend/repl.py:971 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:871 +#: bpython/curtsiesfrontend/repl.py:976 msgid "Auto-reloading not available because watchdog not installed." msgstr "" + diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index d88ebcc9d..5ede109be 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,285 +7,302 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-03-24 00:25+0100\n" +"POT-Creation-Date: 2019-09-22 22:17+0200\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" +"Language: es_ES\n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 1.3\n" +"Generated-By: Babel 2.6.0\n" -#: bpython/args.py:59 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:69 +#: bpython/args.py:73 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:71 +#: bpython/args.py:75 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:74 +#: bpython/args.py:78 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:76 +#: bpython/args.py:80 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "y" msgstr "s" -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "yes" msgstr "si" -#: bpython/cli.py:1695 +#: bpython/cli.py:1705 msgid "Rewind" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1706 msgid "Save" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1707 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1708 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1709 msgid "Show Source" msgstr "" -#: bpython/curtsies.py:37 +#: bpython/curtsies.py:139 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:39 +#: bpython/curtsies.py:141 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:228 +#: bpython/history.py:222 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:94 +#: bpython/paste.py:96 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:96 +#: bpython/paste.py:98 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:100 +#: bpython/paste.py:102 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:103 +#: bpython/paste.py:105 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:109 +#: bpython/paste.py:111 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:549 +#: bpython/repl.py:672 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:554 +#: bpython/repl.py:677 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:559 +#: bpython/repl.py:682 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:561 +#: bpython/repl.py:684 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:694 +#: bpython/repl.py:815 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 +#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:709 +#: bpython/repl.py:830 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:713 +#: bpython/repl.py:834 msgid "overwrite" msgstr "" -#: bpython/repl.py:715 +#: bpython/repl.py:836 msgid "append" msgstr "" -#: bpython/repl.py:727 bpython/repl.py:1022 +#: bpython/repl.py:848 bpython/repl.py:1140 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:729 +#: bpython/repl.py:850 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:735 +#: bpython/repl.py:856 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:863 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:744 +#: bpython/repl.py:865 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:753 +#: bpython/repl.py:874 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:754 +#: bpython/repl.py:875 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:761 +#: bpython/repl.py:882 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:768 +#: bpython/repl.py:888 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:772 +#: bpython/repl.py:892 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:780 +#: bpython/repl.py:900 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:783 +#: bpython/repl.py:903 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:817 +#: bpython/repl.py:938 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:824 bpython/repl.py:828 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:831 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1007 +#: bpython/repl.py:1126 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1029 +#: bpython/repl.py:1148 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1032 -msgid "Error editing config file." +#: bpython/repl.py:1151 +#, python-format +msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:619 +#: bpython/urwid.py:622 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra el " "código fuente" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1130 +#: bpython/urwid.py:1138 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1141 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1135 +#: bpython/urwid.py:1143 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1146 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:344 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:340 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:565 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:582 +#: bpython/curtsiesfrontend/repl.py:660 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:855 +#: bpython/curtsiesfrontend/repl.py:929 +msgid "Session not reevaluated because it was not edited" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:941 +msgid "Session not reevaluated because saved file was blank" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:950 +msgid "Session edited and reevaluated" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:960 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:861 +#: bpython/curtsiesfrontend/repl.py:966 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:866 +#: bpython/curtsiesfrontend/repl.py:971 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:871 +#: bpython/curtsiesfrontend/repl.py:976 msgid "Auto-reloading not available because watchdog not installed." msgstr "" +#~ msgid "Error editing config file." +#~ msgstr "" + diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index cc326c2de..53dd059ba 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,295 +6,309 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-03-24 00:25+0100\n" +"POT-Creation-Date: 2019-09-22 22:17+0200\n" "PO-Revision-Date: 2015-03-24 00:29+0100\n" "Last-Translator: Sebastian Ramacher \n" +"Language: fr_FR\n" "Language-Team: bpython developers\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Plural-Forms: nplurals=2; plural=(n > 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 1.3\n" -"Language: fr_FR\n" -"X-Generator: Poedit 1.6.10\n" +"Generated-By: Babel 2.6.0\n" -#: bpython/args.py:59 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back to " -"the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back " +"to the regular Python interpreter." msgstr "" "Utilisation: %prog [options] [fichier [arguments]]\n" -"NOTE: Si bpython ne reconnaît pas un des arguments fournis, l'interpréteur " -"Python classique sera lancé" +"NOTE: Si bpython ne reconnaît pas un des arguments fournis, " +"l'interpréteur Python classique sera lancé" -#: bpython/args.py:69 +#: bpython/args.py:73 msgid "Use CONFIG instead of default config file." msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." -#: bpython/args.py:71 +#: bpython/args.py:75 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -"Aller dans le shell bpython après l'exécution du fichier au lieu de quitter." +"Aller dans le shell bpython après l'exécution du fichier au lieu de " +"quitter." -#: bpython/args.py:74 +#: bpython/args.py:78 msgid "Don't flush the output to stdout." msgstr "Ne pas purger la sortie vers stdout." -#: bpython/args.py:76 +#: bpython/args.py:80 msgid "Print version and exit." msgstr "Afficher la version et quitter." -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "y" msgstr "o" -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "yes" msgstr "oui" -#: bpython/cli.py:1695 +#: bpython/cli.py:1705 msgid "Rewind" msgstr "Rembobiner" -#: bpython/cli.py:1696 +#: bpython/cli.py:1706 msgid "Save" msgstr "Sauvegarder" -#: bpython/cli.py:1697 +#: bpython/cli.py:1707 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1708 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1709 msgid "Show Source" msgstr "Montrer le code source" -#: bpython/curtsies.py:37 +#: bpython/curtsies.py:139 msgid "log debug messages to bpython.log" msgstr "logger les messages de debug dans bpython.log" -#: bpython/curtsies.py:39 +#: bpython/curtsies.py:141 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:228 +#: bpython/history.py:222 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" -#: bpython/paste.py:94 +#: bpython/paste.py:96 msgid "Helper program not found." msgstr "programme externe non trouvé." -#: bpython/paste.py:96 +#: bpython/paste.py:98 msgid "Helper program could not be run." msgstr "impossible de lancer le programme externe." -#: bpython/paste.py:100 +#: bpython/paste.py:102 #, python-format msgid "Helper program returned non-zero exit status %d." -msgstr "" -"le programme externe a renvoyé un statut de sortie différent de zéro %d." +msgstr "le programme externe a renvoyé un statut de sortie différent de zéro %d." -#: bpython/paste.py:103 +#: bpython/paste.py:105 msgid "No output from helper program." msgstr "pas de sortie du programme externe." -#: bpython/paste.py:109 +#: bpython/paste.py:111 msgid "Failed to recognize the helper program's output as an URL." msgstr "la sortie du programme externe ne correspond pas à une URL." -#: bpython/repl.py:549 +#: bpython/repl.py:672 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:554 +#: bpython/repl.py:677 #, python-format msgid "Cannot get source: %s" msgstr "Impossible de récupérer le source: %s" -#: bpython/repl.py:559 +#: bpython/repl.py:682 #, python-format msgid "Cannot access source of %r" msgstr "Impossible d'accéder au source de %r" -#: bpython/repl.py:561 +#: bpython/repl.py:684 #, python-format msgid "No source code found for %s" msgstr "Pas de code source trouvé pour %s" -#: bpython/repl.py:694 +#: bpython/repl.py:815 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 +#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:709 +#: bpython/repl.py:830 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:713 +#: bpython/repl.py:834 msgid "overwrite" msgstr "" -#: bpython/repl.py:715 +#: bpython/repl.py:836 msgid "append" msgstr "" -#: bpython/repl.py:727 bpython/repl.py:1022 +#: bpython/repl.py:848 bpython/repl.py:1140 #, python-format msgid "Error writing file '%s': %s" msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" -#: bpython/repl.py:729 +#: bpython/repl.py:850 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:735 +#: bpython/repl.py:856 msgid "No clipboard available." msgstr "Pas de presse-papier disponible." -#: bpython/repl.py:742 +#: bpython/repl.py:863 msgid "Could not copy to clipboard." msgstr "Impossible de copier vers le presse-papier." -#: bpython/repl.py:744 +#: bpython/repl.py:865 msgid "Copied content to clipboard." msgstr "Contenu copié vers le presse-papier." -#: bpython/repl.py:753 +#: bpython/repl.py:874 msgid "Pastebin buffer? (y/N) " msgstr "Tampon Pastebin ? (o/N) " -#: bpython/repl.py:754 +#: bpython/repl.py:875 msgid "Pastebin aborted." msgstr "Pastebin abandonné." -#: bpython/repl.py:761 +#: bpython/repl.py:882 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" -#: bpython/repl.py:768 +#: bpython/repl.py:888 msgid "Posting data to pastebin..." msgstr "Envoi des donnés à pastebin..." -#: bpython/repl.py:772 +#: bpython/repl.py:892 #, python-format msgid "Upload failed: %s" msgstr "Echec du téléchargement: %s" -#: bpython/repl.py:780 +#: bpython/repl.py:900 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "URL Pastebin: %s - URL de suppression: %s" -#: bpython/repl.py:783 +#: bpython/repl.py:903 #, python-format msgid "Pastebin URL: %s" msgstr "URL Pastebin: %s" -#: bpython/repl.py:817 +#: bpython/repl.py:938 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:824 bpython/repl.py:828 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:831 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1007 +#: bpython/repl.py:1126 msgid "Config file does not exist - create new from default? (y/N)" -msgstr "" -"Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" +msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" -#: bpython/repl.py:1029 +#: bpython/repl.py:1148 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1032 -msgid "Error editing config file." -msgstr "" +#: bpython/repl.py:1151 +#, fuzzy, python-format +msgid "Error editing config file: %s" +msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" -#: bpython/urwid.py:619 +#: bpython/urwid.py:622 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -" <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> Montrer " -"Source " +" <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> " +"Montrer Source " -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "Lancer le reactor twisted." -#: bpython/urwid.py:1130 +#: bpython/urwid.py:1138 msgid "Select specific reactor (see --help-reactors). Implies --twisted." -msgstr "" -"Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." +msgstr "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1141 msgid "List available reactors for -r." msgstr "Lister les reactors disponibles pour -r." -#: bpython/urwid.py:1135 +#: bpython/urwid.py:1143 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" pour " -"donner plus d'options au plugin." +"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" " +"pour donner plus d'options au plugin." -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1146 msgid "Port to run an eval server on (forces Twisted)." msgstr "Port pour lancer un server eval (force Twisted)." -#: bpython/curtsiesfrontend/repl.py:344 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:340 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." -#: bpython/curtsiesfrontend/repl.py:565 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" -#: bpython/curtsiesfrontend/repl.py:582 +#: bpython/curtsiesfrontend/repl.py:660 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:855 +#: bpython/curtsiesfrontend/repl.py:929 +msgid "Session not reevaluated because it was not edited" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:941 +msgid "Session not reevaluated because saved file was blank" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:950 +msgid "Session edited and reevaluated" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:960 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:861 +#: bpython/curtsiesfrontend/repl.py:966 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:866 +#: bpython/curtsiesfrontend/repl.py:971 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:871 +#: bpython/curtsiesfrontend/repl.py:976 msgid "Auto-reloading not available because watchdog not installed." msgstr "" + +#~ msgid "Error editing config file." +#~ msgstr "" + diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index 66cd0d5a5..aaa50c495 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,283 +7,300 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-03-24 00:25+0100\n" +"POT-Creation-Date: 2019-09-22 22:17+0200\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" +"Language: it_IT\n" "Language-Team: Michele Orrù\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 1.3\n" +"Generated-By: Babel 2.6.0\n" -#: bpython/args.py:59 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:69 +#: bpython/args.py:73 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:71 +#: bpython/args.py:75 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:74 +#: bpython/args.py:78 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:76 +#: bpython/args.py:80 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "y" msgstr "s" -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "yes" msgstr "si" -#: bpython/cli.py:1695 +#: bpython/cli.py:1705 msgid "Rewind" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1706 msgid "Save" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1707 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1708 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1709 msgid "Show Source" msgstr "" -#: bpython/curtsies.py:37 +#: bpython/curtsies.py:139 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:39 +#: bpython/curtsies.py:141 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:228 +#: bpython/history.py:222 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:94 +#: bpython/paste.py:96 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:96 +#: bpython/paste.py:98 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:100 +#: bpython/paste.py:102 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:103 +#: bpython/paste.py:105 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:109 +#: bpython/paste.py:111 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:549 +#: bpython/repl.py:672 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:554 +#: bpython/repl.py:677 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:559 +#: bpython/repl.py:682 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:561 +#: bpython/repl.py:684 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:694 +#: bpython/repl.py:815 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 +#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:709 +#: bpython/repl.py:830 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:713 +#: bpython/repl.py:834 msgid "overwrite" msgstr "" -#: bpython/repl.py:715 +#: bpython/repl.py:836 msgid "append" msgstr "" -#: bpython/repl.py:727 bpython/repl.py:1022 +#: bpython/repl.py:848 bpython/repl.py:1140 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:729 +#: bpython/repl.py:850 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:735 +#: bpython/repl.py:856 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:863 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:744 +#: bpython/repl.py:865 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:753 +#: bpython/repl.py:874 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:754 +#: bpython/repl.py:875 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:761 +#: bpython/repl.py:882 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:768 +#: bpython/repl.py:888 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:772 +#: bpython/repl.py:892 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:780 +#: bpython/repl.py:900 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:783 +#: bpython/repl.py:903 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:817 +#: bpython/repl.py:938 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:824 bpython/repl.py:828 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:831 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1007 +#: bpython/repl.py:1126 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1029 +#: bpython/repl.py:1148 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1032 -msgid "Error editing config file." +#: bpython/repl.py:1151 +#, python-format +msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:619 +#: bpython/urwid.py:622 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1130 +#: bpython/urwid.py:1138 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1141 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1135 +#: bpython/urwid.py:1143 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1146 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:344 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:340 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:565 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:582 +#: bpython/curtsiesfrontend/repl.py:660 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:855 +#: bpython/curtsiesfrontend/repl.py:929 +msgid "Session not reevaluated because it was not edited" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:941 +msgid "Session not reevaluated because saved file was blank" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:950 +msgid "Session edited and reevaluated" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:960 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:861 +#: bpython/curtsiesfrontend/repl.py:966 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:866 +#: bpython/curtsiesfrontend/repl.py:971 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:871 +#: bpython/curtsiesfrontend/repl.py:976 msgid "Auto-reloading not available because watchdog not installed." msgstr "" +#~ msgid "Error editing config file." +#~ msgstr "" + diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index c82908ee7..bd4e52d52 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,283 +7,300 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2015-03-24 00:25+0100\n" +"POT-Creation-Date: 2019-09-22 22:17+0200\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" +"Language: nl_NL\n" "Language-Team: bpython developers\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 1.3\n" +"Generated-By: Babel 2.6.0\n" -#: bpython/args.py:59 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:69 +#: bpython/args.py:73 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:71 +#: bpython/args.py:75 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:74 +#: bpython/args.py:78 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:76 +#: bpython/args.py:80 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "y" msgstr "j" -#: bpython/cli.py:318 bpython/urwid.py:557 +#: bpython/cli.py:319 bpython/urwid.py:560 msgid "yes" msgstr "ja" -#: bpython/cli.py:1695 +#: bpython/cli.py:1705 msgid "Rewind" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1706 msgid "Save" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1707 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1708 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1709 msgid "Show Source" msgstr "" -#: bpython/curtsies.py:37 +#: bpython/curtsies.py:139 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:39 +#: bpython/curtsies.py:141 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:228 +#: bpython/history.py:222 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:94 +#: bpython/paste.py:96 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:96 +#: bpython/paste.py:98 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:100 +#: bpython/paste.py:102 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:103 +#: bpython/paste.py:105 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:109 +#: bpython/paste.py:111 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:549 +#: bpython/repl.py:672 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:554 +#: bpython/repl.py:677 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:559 +#: bpython/repl.py:682 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:561 +#: bpython/repl.py:684 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:694 +#: bpython/repl.py:815 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:696 bpython/repl.py:699 bpython/repl.py:718 +#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:709 +#: bpython/repl.py:830 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:713 +#: bpython/repl.py:834 msgid "overwrite" msgstr "" -#: bpython/repl.py:715 +#: bpython/repl.py:836 msgid "append" msgstr "" -#: bpython/repl.py:727 bpython/repl.py:1022 +#: bpython/repl.py:848 bpython/repl.py:1140 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:729 +#: bpython/repl.py:850 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:735 +#: bpython/repl.py:856 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:742 +#: bpython/repl.py:863 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:744 +#: bpython/repl.py:865 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:753 +#: bpython/repl.py:874 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:754 +#: bpython/repl.py:875 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:761 +#: bpython/repl.py:882 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:768 +#: bpython/repl.py:888 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:772 +#: bpython/repl.py:892 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:780 +#: bpython/repl.py:900 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:783 +#: bpython/repl.py:903 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:817 +#: bpython/repl.py:938 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:824 bpython/repl.py:828 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:831 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1007 +#: bpython/repl.py:1126 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1029 +#: bpython/repl.py:1148 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1032 -msgid "Error editing config file." +#: bpython/repl.py:1151 +#, python-format +msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:619 +#: bpython/urwid.py:622 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1130 +#: bpython/urwid.py:1138 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1141 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1135 +#: bpython/urwid.py:1143 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1146 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:344 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:340 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:565 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:582 +#: bpython/curtsiesfrontend/repl.py:660 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:855 +#: bpython/curtsiesfrontend/repl.py:929 +msgid "Session not reevaluated because it was not edited" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:941 +msgid "Session not reevaluated because saved file was blank" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:950 +msgid "Session edited and reevaluated" +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:960 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:861 +#: bpython/curtsiesfrontend/repl.py:966 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:866 +#: bpython/curtsiesfrontend/repl.py:971 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:871 +#: bpython/curtsiesfrontend/repl.py:976 msgid "Auto-reloading not available because watchdog not installed." msgstr "" +#~ msgid "Error editing config file." +#~ msgstr "" + From a67c2d37ebd941037d1e6cb4e26b8ac4fb1db267 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 22 Sep 2019 22:54:34 +0200 Subject: [PATCH 0910/1650] Update translations --- .../translations/de/LC_MESSAGES/bpython.po | 39 +++++++++++-------- .../translations/fr_FR/LC_MESSAGES/bpython.po | 36 ++++++++--------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 41a63bdab..fdb8d8bc3 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -8,22 +8,26 @@ msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" "POT-Creation-Date: 2019-09-22 22:17+0200\n" -"PO-Revision-Date: 2019-09-22 22:21+0200\n" +"PO-Revision-Date: 2019-09-22 22:54+0200\n" "Last-Translator: Sebastian Ramacher \n" "Language: de\n" "Language-Team: de \n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.6.0\n" +"X-Generator: Poedit 2.2.3\n" #: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back " -"to the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back to the " +"regular Python interpreter." msgstr "" +"Verwendung: %prog [Optionen] [Datei [Argumente]]\n" +"Hinweis: Wenn bpython Argumente übergeben bekommt, die nicht verstanden werden, " +"wird der normale Python Interpreter ausgeführt." #: bpython/args.py:73 msgid "Use CONFIG instead of default config file." @@ -216,14 +220,13 @@ msgstr[1] "" #: bpython/repl.py:1126 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " -"werden? (j/N)" +"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt werden? (j/N)" #: bpython/repl.py:1148 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -"bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " -"Änderungen übernommen werden." +"bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die Änderungen " +"übernommen werden." #: bpython/repl.py:1151 #, python-format @@ -237,21 +240,23 @@ msgstr "" #: bpython/urwid.py:1136 msgid "Run twisted reactor." -msgstr "" +msgstr "Führe twisted reactor aus." #: bpython/urwid.py:1138 msgid "Select specific reactor (see --help-reactors). Implies --twisted." -msgstr "" +msgstr "Wähle reactor aus (siehe --help-reactors). Impliziert --twisted." #: bpython/urwid.py:1141 msgid "List available reactors for -r." -msgstr "" +msgstr "Liste verfügbare reactors für -r auf." #: bpython/urwid.py:1143 msgid "" -"twistd plugin to run (use twistd for a list). Use \"--\" to pass further " -"options to the plugin." +"twistd plugin to run (use twistd for a list). Use \"--\" to pass further options " +"to the plugin." msgstr "" +"Auszuführendes twistd Plugin (starte twistd für eine Liste). Verwende \"--\" um " +"Optionen an das Plugin zu übergeben." #: bpython/urwid.py:1146 msgid "Port to run an eval server on (forces Twisted)." @@ -274,7 +279,7 @@ msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" #: bpython/curtsiesfrontend/repl.py:660 #, python-format msgid "Reloaded at %s because %s modified." -msgstr "" +msgstr "Bei %s neugeladen, da %s modifiziert wurde." #: bpython/curtsiesfrontend/repl.py:929 msgid "Session not reevaluated because it was not edited" @@ -295,13 +300,13 @@ msgstr "Bei %s vom Benutzer neu geladen." #: bpython/curtsiesfrontend/repl.py:966 msgid "Auto-reloading deactivated." -msgstr "" +msgstr "Automatisches Neuladen deaktiviert." #: bpython/curtsiesfrontend/repl.py:971 msgid "Auto-reloading active, watching for file changes..." -msgstr "" +msgstr "Automatisches Neuladen ist aktiv; beobachte Dateiänderungen..." #: bpython/curtsiesfrontend/repl.py:976 msgid "Auto-reloading not available because watchdog not installed." msgstr "" - +"Automatisches Neuladen ist nicht verfügbar da watchdog nicht installiert ist." diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index 53dd059ba..d16700c75 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -7,25 +7,26 @@ msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" "POT-Creation-Date: 2019-09-22 22:17+0200\n" -"PO-Revision-Date: 2015-03-24 00:29+0100\n" +"PO-Revision-Date: 2019-09-22 22:58+0200\n" "Last-Translator: Sebastian Ramacher \n" "Language: fr_FR\n" "Language-Team: bpython developers\n" -"Plural-Forms: nplurals=2; plural=(n > 1)\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.6.0\n" +"X-Generator: Poedit 2.2.3\n" #: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back " -"to the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back to the " +"regular Python interpreter." msgstr "" "Utilisation: %prog [options] [fichier [arguments]]\n" -"NOTE: Si bpython ne reconnaît pas un des arguments fournis, " -"l'interpréteur Python classique sera lancé" +"NOTE: Si bpython ne reconnaît pas un des arguments fournis, l'interpréteur " +"Python classique sera lancé" #: bpython/args.py:73 msgid "Use CONFIG instead of default config file." @@ -34,8 +35,7 @@ msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." #: bpython/args.py:75 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -"Aller dans le shell bpython après l'exécution du fichier au lieu de " -"quitter." +"Aller dans le shell bpython après l'exécution du fichier au lieu de quitter." #: bpython/args.py:78 msgid "Don't flush the output to stdout." @@ -226,16 +226,16 @@ msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" #: bpython/repl.py:1151 -#, fuzzy, python-format +#, python-format msgid "Error editing config file: %s" -msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" +msgstr "" #: bpython/urwid.py:622 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -" <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> " -"Montrer Source " +" <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> Montrer " +"Source " #: bpython/urwid.py:1136 msgid "Run twisted reactor." @@ -251,11 +251,11 @@ msgstr "Lister les reactors disponibles pour -r." #: bpython/urwid.py:1143 msgid "" -"twistd plugin to run (use twistd for a list). Use \"--\" to pass further " -"options to the plugin." +"twistd plugin to run (use twistd for a list). Use \"--\" to pass further options " +"to the plugin." msgstr "" -"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" " -"pour donner plus d'options au plugin." +"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" pour " +"donner plus d'options au plugin." #: bpython/urwid.py:1146 msgid "Port to run an eval server on (forces Twisted)." @@ -308,7 +308,3 @@ msgstr "" #: bpython/curtsiesfrontend/repl.py:976 msgid "Auto-reloading not available because watchdog not installed." msgstr "" - -#~ msgid "Error editing config file." -#~ msgstr "" - From 54e83c82e55f3c5f8e9336d30db304660fcbb64f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 3 Oct 2019 18:24:14 +0200 Subject: [PATCH 0911/1650] Remove assignments to local variables which are never used --- bpython/cli.py | 1 - bpython/repl.py | 2 +- bpython/urwid.py | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 5cd24261e..6c17a2999 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1298,7 +1298,6 @@ def lsize(): rows += 1 if rows + 2 >= max_h: - rows = max_h - 2 return False shared.rows = rows diff --git a/bpython/repl.py b/bpython/repl.py index cf87ad097..f86a2366f 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -236,7 +236,7 @@ def showtraceback(self): l.insert(0, "Traceback (most recent call last):\n") l[len(l):] = traceback.format_exception_only(t, v) finally: - tblist = tb = None + pass self.writetb(l) diff --git a/bpython/urwid.py b/bpython/urwid.py index 8d575dc61..a4e215219 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1103,8 +1103,6 @@ def tab(self, back=False): cw = self.current_string() or self.cw() if not cw: return True - else: - cw = self.matches_iter.current_word if self.matches_iter.is_cseq(): cursor, text = self.matches_iter.substitute_cseq() From d7117359858bd312594c935e2efc6571a0097e82 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 3 Oct 2019 18:29:15 +0200 Subject: [PATCH 0912/1650] Remove unused imports --- bpython/config.py | 1 - bpython/curtsiesfrontend/interpreter.py | 1 - bpython/importcompletion.py | 3 +-- bpython/simpleeval.py | 2 -- 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index aca3c2f5b..d9a2147cd 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -10,7 +10,6 @@ from six.moves.configparser import ConfigParser from .autocomplete import SIMPLE as default_completion, ALL_MODES -from .keys import cli_key_dispatch as cli_key_dispatch class Struct(object): diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index e1a484802..c002cc797 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -11,7 +11,6 @@ from bpython.curtsiesfrontend.parse import parse from bpython.repl import Interpreter as ReplInterpreter -from bpython.config import getpreferredencoding default_colors = { diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 5dbe76a6d..0a243ec98 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -32,7 +32,6 @@ import os import sys import warnings -from warnings import catch_warnings from six.moves import filter if py3: @@ -146,7 +145,7 @@ def find_modules(path): # Workaround for issue #166 continue try: - with catch_warnings(): + with warnings.catch_warnings(): warnings.simplefilter("ignore", ImportWarning) fo, pathname, _ = imp.find_module(name, [path]) except (ImportError, IOError, SyntaxError): diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 51b50e7ac..986fcad31 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -34,8 +34,6 @@ import inspect from six import string_types from six.moves import builtins -import sys -import types from . import line as line_properties from ._py3compat import py3 From d9402cdc69c91f9f194077afbe29f4639a317259 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 4 Oct 2019 23:34:28 +0200 Subject: [PATCH 0913/1650] Call base's __init__ --- bpython/cli.py | 4 ++-- bpython/curtsiesfrontend/events.py | 2 +- bpython/curtsiesfrontend/interaction.py | 3 +++ bpython/curtsiesfrontend/repl.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 6c17a2999..14415a3a6 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -307,7 +307,7 @@ def make_colors(config): class CLIInteraction(repl.Interaction): def __init__(self, config, statusbar=None): - repl.Interaction.__init__(self, config, statusbar) + super(CLIInteraction, self).__init__(config, statusbar) def confirm(self, q): """Ask for yes or no and return boolean""" @@ -328,7 +328,7 @@ def file_prompt(self, s): class CLIRepl(repl.Repl): def __init__(self, scr, interp, statusbar, config, idle=None): - repl.Repl.__init__(self, interp, config) + super(CLIRepl, self).__init__(interp, config) self.interp.writetb = self.writetb self.scr = scr self.stdout_hist = '' # native str (bytes in Py2, unicode in Py3) diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index 065a8dc41..b7766fbe0 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -27,7 +27,7 @@ class ScheduledRefreshRequestEvent(curtsies.events.ScheduledEvent): Used to schedule the disappearance of status bar message that only shows for a few seconds""" def __init__(self, when): - self.when = when # time.time() + how long + super(ScheduledRefreshRequestEvent, self).__init__(when) def __repr__(self): return ("" % diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 5505e367f..80d1f45a1 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -27,6 +27,7 @@ class StatusBar(BpythonInteraction): bpython.Repl code. """ def __init__(self, + config, permanent_text="", request_refresh=lambda: None, schedule_refresh=lambda when: None): @@ -47,6 +48,8 @@ def __init__(self, self.request_refresh = request_refresh self.schedule_refresh = schedule_refresh + super(StatusBar, self).__init__(config) + def push_permanent_message(self, msg): self._message = '' self.permanent_stack.append(msg) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d36997a22..6b415fabd 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -348,7 +348,7 @@ def __init__(self, self.reevaluating = False self.fake_refresh_requested = False - self.status_bar = StatusBar('', + self.status_bar = StatusBar(config, '', request_refresh=self.request_refresh, schedule_refresh=self.schedule_refresh) self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch) From 072576a7a83e7f47458aa994f507cad3a593e402 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 4 Oct 2019 23:42:40 +0200 Subject: [PATCH 0914/1650] Use super where possible --- bpython/curtsies.py | 12 ++++++------ bpython/curtsiesfrontend/interpreter.py | 4 ++-- bpython/formatter.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 792f20b1c..92e4c8452 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -58,12 +58,12 @@ def __init__(self, config, locals_, banner, interp=None): with self.input_generator: pass # temp hack to get .original_stty - BaseRepl.__init__(self, - locals_=locals_, - config=config, - banner=banner, - interp=interp, - orig_tcattrs=self.input_generator.original_stty) + super(FullCurtsiesRepl, self).__init__( + locals_=locals_, + config=config, + banner=banner, + interp=interp, + orig_tcattrs=self.input_generator.original_stty) def get_term_hw(self): return self.window.get_term_hw() diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index c002cc797..139039f5b 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -49,7 +49,7 @@ def __init__(self, color_scheme, **options): self.f_strings = {} for k, v in iteritems(color_scheme): self.f_strings[k] = '\x01%s' % (v,) - Formatter.__init__(self, **options) + super(BPythonFormatter, self).__init__(**options) def format(self, tokensource, outfile): o = '' @@ -68,7 +68,7 @@ def __init__(self, locals=None, encoding=None): We include an argument for the outfile to pass to the formatter for it to write to. """ - ReplInterpreter.__init__(self, locals, encoding) + super(Interp, self).__init__(locals, encoding) # typically changed after being instantiated # but used when interpreter used corresponding REPL diff --git a/bpython/formatter.py b/bpython/formatter.py index 4d916c626..23bf0ccbd 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -96,7 +96,7 @@ def __init__(self, color_scheme, **options): # FIXME: Find a way to make this the inverse of the current # background colour self.f_strings[k] += 'I' - Formatter.__init__(self, **options) + super(BPythonFormatter, self).__init__(**options) def format(self, tokensource, outfile): o = '' From 14c17c10ea668b86ff291e51567bd1fbb8d5d921 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 5 Oct 2019 09:45:21 +0200 Subject: [PATCH 0915/1650] Turn Interpreter into a new-style class --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index f86a2366f..8387f2f22 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -81,7 +81,7 @@ def estimate(self): return self.running_time - self.last_command -class Interpreter(code.InteractiveInterpreter): +class Interpreter(code.InteractiveInterpreter, object): """Source code interpreter for use in bpython.""" bpython_input_re = LazyReCompile(r'') From 57aa616d0a61276f6b87cbb7b96fcd253b7c5bc7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 13 Oct 2019 15:39:33 +0200 Subject: [PATCH 0916/1650] Also protect remaining part of get_args from user code exceptions (fixes #776) --- bpython/repl.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 8387f2f22..d7f48a7b8 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -626,6 +626,24 @@ def get_args(self): fake_cursor, self.current_line, self.interp.locals) except simpleeval.EvaluationError: return False + + if inspect.isclass(f): + class_f = None + + if (hasattr(f, '__init__') and + f.__init__ is not object.__init__): + class_f = f.__init__ + if ((not class_f or + not inspection.getfuncprops(func, class_f)) and + hasattr(f, '__new__') and + f.__new__ is not object.__new__ and + # py3 + f.__new__.__class__ is not object.__new__.__class__): + + class_f = f.__new__ + + if class_f: + f = class_f except Exception: # another case of needing to catch every kind of error # since user code is run in the case of descriptors @@ -633,24 +651,6 @@ def get_args(self): # stuff ! return False - if inspect.isclass(f): - class_f = None - - if (hasattr(f, '__init__') and - f.__init__ is not object.__init__): - class_f = f.__init__ - if ((not class_f or - not inspection.getfuncprops(func, class_f)) and - hasattr(f, '__new__') and - f.__new__ is not object.__new__ and - # py3 - f.__new__.__class__ is not object.__new__.__class__): - - class_f = f.__new__ - - if class_f: - f = class_f - self.current_func = f self.funcprops = inspection.getfuncprops(func, f) if self.funcprops: From 0a6e34c99432b82bda2d027636e2260af54e16c2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 21 Oct 2019 10:41:40 +0200 Subject: [PATCH 0917/1650] Use os.O_TRUNC --- bpython/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/history.py b/bpython/history.py index ae9c31628..de518439a 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -182,7 +182,7 @@ def load_from(self, fd): return entries if len(entries) else [''] def save(self, filename, encoding, lines=0): - fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.TRUNC, + fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, stat.S_IRUSR | stat.S_IWUSR) with io.open(fd, 'w', encoding=encoding, errors='ignore') as hfile: From afc0eb325eeb54497c0f7db527ff6540b25b91b7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 21 Oct 2019 10:47:20 +0200 Subject: [PATCH 0918/1650] Mark as not locked before releasing the lock --- bpython/filelock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/filelock.py b/bpython/filelock.py index e956c5543..32044fcd3 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -85,8 +85,8 @@ def acquire(self): raise e def release(self): - fcntl.flock(self.fileobj, fcntl.LOCK_UN) self.locked = False + fcntl.flock(self.fileobj, fcntl.LOCK_UN) class WindowsFileLock(BaseLock): @@ -101,8 +101,8 @@ def acquire(self): self.locked = True def release(self): - msvcrt.locking(self.fileobj.fileno(), msvcrt.LK_UNLCK, 1) self.locked = False + msvcrt.locking(self.fileobj.fileno(), msvcrt.LK_UNLCK, 1) if has_fcntl: From 52580b49d65587ff2fb799b400153091248716b5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 23 Oct 2019 20:28:19 +0200 Subject: [PATCH 0919/1650] Create a lock file on Windows --- bpython/filelock.py | 28 +++++++++++++++++++++------- bpython/history.py | 6 +++--- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/bpython/filelock.py b/bpython/filelock.py index 32044fcd3..9a646f03f 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -2,7 +2,7 @@ # The MIT License # -# Copyright (c) 2015-2016 Sebastian Ramacher +# Copyright (c) 2015-2019 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -33,6 +33,7 @@ try: import msvcrt + import os has_msvcrt = True except ImportError: has_msvcrt = False @@ -42,7 +43,7 @@ class BaseLock(object): """Base class for file locking """ - def __init__(self, fileobj): + def __init__(self, fileobj, mode=None, filename=None): self.fileobj = fileobj self.locked = False @@ -69,7 +70,7 @@ class UnixFileLock(BaseLock): """Simple file locking for Unix using fcntl """ - def __init__(self, fileobj, mode=None): + def __init__(self, fileobj, mode=None, filename=None): super(UnixFileLock, self).__init__(fileobj) if mode is None: @@ -93,16 +94,29 @@ class WindowsFileLock(BaseLock): """Simple file locking for Windows using msvcrt """ - def __init__(self, fileobj, mode=None): - super(WindowsFileLock, self).__init__(fileobj) + def __init__(self, fileobj, mode=None, filename=None): + super(WindowsFileLock, self).__init__(None) + self.filename = "{}.lock".format(filename) def acquire(self): - msvcrt.locking(self.fileobj.fileno(), msvcrt.LK_NBLCK, 1) + # create a lock file and lock it + self.fileobj = os.open(self.filename, os.O_RDWR | os.O_CREAT | os.O_TRUNC) + msvcrt.locking(self.fileobj, msvcrt.LK_NBLCK, 1) + self.locked = True def release(self): self.locked = False - msvcrt.locking(self.fileobj.fileno(), msvcrt.LK_UNLCK, 1) + + # unlock lock file and remove it + msvcrt.locking(self.fileobj, msvcrt.LK_UNLCK, 1) + self.fileobj.close() + self.fileobj = None + + try: + os.remove(self.filename) + except OSError: + pass if has_fcntl: diff --git a/bpython/history.py b/bpython/history.py index de518439a..edcf8b500 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -172,7 +172,7 @@ def reset(self): def load(self, filename, encoding): with io.open(filename, 'r', encoding=encoding, errors='ignore') as hfile: - with FileLock(hfile): + with FileLock(hfile, filename=filename): self.entries = self.load_from(hfile) def load_from(self, fd): @@ -186,7 +186,7 @@ def save(self, filename, encoding, lines=0): stat.S_IRUSR | stat.S_IWUSR) with io.open(fd, 'w', encoding=encoding, errors='ignore') as hfile: - with FileLock(hfile): + with FileLock(hfile, filename=filename): self.save_to(hfile, self.entries, lines) def save_to(self, fd, entries=None, lines=0): @@ -205,7 +205,7 @@ def append_reload_and_write(self, s, filename, encoding): stat.S_IRUSR | stat.S_IWUSR) with io.open(fd, 'a+', encoding=encoding, errors='ignore') as hfile: - with FileLock(hfile): + with FileLock(hfile, filename=filename): # read entries hfile.seek(0, os.SEEK_SET) entries = self.load_from(hfile) From 22db98ca55793da866ef2ada11adaddc44973ff7 Mon Sep 17 00:00:00 2001 From: myrmica-habilis Date: Fri, 25 Oct 2019 10:14:41 +0200 Subject: [PATCH 0920/1650] Close lock file using os close() --- bpython/filelock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/filelock.py b/bpython/filelock.py index 9a646f03f..703538738 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -110,7 +110,7 @@ def release(self): # unlock lock file and remove it msvcrt.locking(self.fileobj, msvcrt.LK_UNLCK, 1) - self.fileobj.close() + os.close(self.fileobj) self.fileobj = None try: From 13dabf88131fcf5ae447fd6b4e47c5f81118df47 Mon Sep 17 00:00:00 2001 From: myrmica-habilis Date: Fri, 25 Oct 2019 10:04:29 +0200 Subject: [PATCH 0921/1650] Add test case for accessing the history file --- bpython/test/test_history.py | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index 49450e1b2..ac2b9b4d2 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -1,7 +1,11 @@ # encoding: utf-8 +import io +import os + from six.moves import range +from bpython.config import getpreferredencoding from bpython.history import History from bpython.test import unittest @@ -81,3 +85,46 @@ def test_reset(self): self.assertEqual(self.history.back(), '#999') self.assertEqual(self.history.forward(), '') + + +class TestHistoryFileAccess(unittest.TestCase): + def setUp(self): + self.filename = 'history_temp_file' + self.encoding = getpreferredencoding() + + with io.open(self.filename, 'w', encoding=self.encoding, + errors='ignore') as f: + f.write('#1\n#2\n') + + def test_load(self): + history = History() + + history.load(self.filename, self.encoding) + self.assertEqual(history.entries, ['#1', '#2']) + + def test_append_reload_and_write(self): + history = History() + + history.append_reload_and_write('#3', self.filename, self.encoding) + self.assertEqual(history.entries, ['#1', '#2', '#3']) + + history.append_reload_and_write('#4', self.filename, self.encoding) + self.assertEqual(history.entries, ['#1', '#2', '#3', '#4']) + + def test_save(self): + history = History(['#1', '#2', '#3', '#4']) + + # save only last 2 lines + history.save(self.filename, self.encoding, lines=2) + + # empty the list of entries and load again from the file + history.entries = [''] + history.load(self.filename, self.encoding) + + self.assertEqual(history.entries, ['#3', '#4']) + + def tearDown(self): + try: + os.remove(self.filename) + except OSError: + pass From 9d17172a03220cad27a5a46ee074a6aa7cd573d1 Mon Sep 17 00:00:00 2001 From: myrmica-habilis Date: Fri, 25 Oct 2019 10:52:20 +0200 Subject: [PATCH 0922/1650] Fix Python 2.7 TypeError --- bpython/test/test_history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index ac2b9b4d2..eb3b2d22f 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -94,7 +94,7 @@ def setUp(self): with io.open(self.filename, 'w', encoding=self.encoding, errors='ignore') as f: - f.write('#1\n#2\n') + f.write(b'#1\n#2\n'.decode()) def test_load(self): history = History() From fabd54870e914244e45d2c097892ebc52c75c74a Mon Sep 17 00:00:00 2001 From: myrmica-habilis Date: Fri, 25 Oct 2019 12:44:01 +0200 Subject: [PATCH 0923/1650] Fix Python 2.7 TypeError in test_save --- bpython/test/test_history.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index eb3b2d22f..01574bd29 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -112,7 +112,10 @@ def test_append_reload_and_write(self): self.assertEqual(history.entries, ['#1', '#2', '#3', '#4']) def test_save(self): - history = History(['#1', '#2', '#3', '#4']) + history = History() + history.entries = [] + for line in ['#1', '#2', '#3', '#4']: + history.append_to(history.entries, line) # save only last 2 lines history.save(self.filename, self.encoding, lines=2) From cde2aadff63b29dc33b0f498e129aa9161778430 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Fri, 22 Nov 2019 12:53:35 +0000 Subject: [PATCH 0924/1650] Blacken the source code and add `black` configuration. - Add configuration file for black's settings. - Add a shield for black's codestyle. - Run black over the entire codebase. --- README.rst | 3 + bpython/__init__.py | 5 +- bpython/__main__.py | 3 +- bpython/_internal.py | 7 +- bpython/_py3compat.py | 14 +- bpython/args.py | 68 +- bpython/autocomplete.py | 270 +++-- bpython/cli.py | 582 +++++----- bpython/clipboard.py | 24 +- bpython/config.py | 407 +++---- bpython/curtsies.py | 80 +- bpython/curtsiesfrontend/_internal.py | 2 +- bpython/curtsiesfrontend/coderunner.py | 23 +- bpython/curtsiesfrontend/events.py | 13 +- bpython/curtsiesfrontend/filewatch.py | 8 +- bpython/curtsiesfrontend/interaction.py | 54 +- bpython/curtsiesfrontend/interpreter.py | 51 +- bpython/curtsiesfrontend/manual_readline.py | 197 ++-- bpython/curtsiesfrontend/parse.py | 61 +- bpython/curtsiesfrontend/preprocess.py | 18 +- bpython/curtsiesfrontend/repl.py | 1052 +++++++++++-------- bpython/curtsiesfrontend/replpainter.py | 211 ++-- bpython/curtsiesfrontend/sitefix.py | 6 +- bpython/filelock.py | 6 +- bpython/formatter.py | 53 +- bpython/history.py | 64 +- bpython/importcompletion.py | 72 +- bpython/inspection.py | 116 +- bpython/keys.py | 35 +- bpython/lazyre.py | 1 + bpython/line.py | 70 +- bpython/pager.py | 9 +- bpython/paste.py | 53 +- bpython/patch_linecache.py | 13 +- bpython/repl.py | 417 ++++---- bpython/simpleeval.py | 62 +- bpython/simplerepl.py | 46 +- bpython/test/__init__.py | 5 +- bpython/test/fodder/original.py | 23 +- bpython/test/fodder/processed.py | 35 +- bpython/test/test_args.py | 60 +- bpython/test/test_autocomplete.py | 358 ++++--- bpython/test/test_config.py | 66 +- bpython/test/test_crashers.py | 34 +- bpython/test/test_curtsies.py | 36 +- bpython/test/test_curtsies_coderunner.py | 22 +- bpython/test/test_curtsies_painting.py | 775 +++++++------- bpython/test/test_curtsies_parser.py | 35 +- bpython/test/test_curtsies_repl.py | 301 +++--- bpython/test/test_filewatch.py | 10 +- bpython/test/test_history.py | 65 +- bpython/test/test_importcompletion.py | 40 +- bpython/test/test_inspection.py | 66 +- bpython/test/test_interpreter.py | 198 ++-- bpython/test/test_keys.py | 46 +- bpython/test/test_line_properties.py | 333 +++--- bpython/test/test_manual_readline.py | 236 +++-- bpython/test/test_preprocess.py | 63 +- bpython/test/test_repl.py | 197 ++-- bpython/test/test_simpleeval.py | 181 ++-- bpython/translations/__init__.py | 21 +- bpython/urwid.py | 484 +++++---- pyproject.toml | 18 + 63 files changed, 4513 insertions(+), 3371 deletions(-) create mode 100644 pyproject.toml diff --git a/README.rst b/README.rst index ed68c8cbc..8264667c5 100644 --- a/README.rst +++ b/README.rst @@ -3,6 +3,9 @@ .. |ImageLink| image:: https://travis-ci.org/bpython/bpython.svg?branch=master .. _ImageLink: https://travis-ci.org/bpython/bpython +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black + *********************************************************************** bpython: A fancy curses interface to the Python interactive interpreter *********************************************************************** diff --git a/bpython/__init__.py b/bpython/__init__.py index 779a1a682..6d4a9aaa0 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -27,7 +27,7 @@ try: from ._version import __version__ as version except ImportError: - version = 'unknown' + version = "unknown" __version__ = version package_dir = os.path.abspath(os.path.dirname(__file__)) @@ -35,7 +35,8 @@ def embed(locals_=None, args=None, banner=None): if args is None: - args = ['-i', '-q'] + args = ["-i", "-q"] from .curtsies import main + return main(args, locals_, banner) diff --git a/bpython/__main__.py b/bpython/__main__.py index d81ec1efd..28c488702 100644 --- a/bpython/__main__.py +++ b/bpython/__main__.py @@ -26,6 +26,7 @@ import sys -if __name__ == '__main__': +if __name__ == "__main__": from .curtsies import main + sys.exit(main()) diff --git a/bpython/_internal.py b/bpython/_internal.py index e0e87ba3c..e3907c7b1 100644 --- a/bpython/_internal.py +++ b/bpython/_internal.py @@ -12,7 +12,6 @@ class _Helper(object): - def __init__(self): if hasattr(pydoc.Helper, "output"): # See issue #228 @@ -21,8 +20,10 @@ def __init__(self): self.helper = pydoc.Helper(sys.stdin, sys.stdout) def __repr__(self): - return ("Type help() for interactive help, " - "or help(object) for help about object.") + return ( + "Type help() for interactive help, " + "or help(object) for help about object." + ) def __call__(self, *args, **kwargs): self.helper(*args, **kwargs) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index 5e6ff3aa8..bebf4efd4 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -39,7 +39,7 @@ import sys import threading -py3 = (sys.version_info[0] == 3) +py3 = sys.version_info[0] == 3 if py3: @@ -49,17 +49,25 @@ if py3 or sys.version_info[:3] >= (2, 7, 3): + def prepare_for_exec(arg, encoding=None): return arg + + else: + def prepare_for_exec(arg, encoding=None): return arg.encode(encoding) if py3: + def try_decode(s, encoding): return s + + else: + def try_decode(s, encoding): """Try to decode s which is str names. Return None if not decodable""" if not isinstance(s, unicode): @@ -71,8 +79,12 @@ def try_decode(s, encoding): if py3: + def is_main_thread(): return threading.main_thread() == threading.current_thread() + + else: + def is_main_thread(): return isinstance(threading.current_thread(), threading._MainThread) diff --git a/bpython/args.py b/bpython/args.py index 1dfe010df..6aea8a197 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -27,8 +27,11 @@ def error(self, msg): def version_banner(): - return 'bpython version %s on top of Python %s %s' % ( - __version__, sys.version.split()[0], sys.executable) + return "bpython version %s on top of Python %s %s" % ( + __version__, + sys.version.split()[0], + sys.executable, + ) def parse(args, extras=None, ignore_stdin=False): @@ -60,24 +63,43 @@ def parse(args, extras=None, ignore_stdin=False): args = sys.argv[1:] parser = RaisingOptionParser( - usage=_('Usage: %prog [options] [file [args]]\n' - 'NOTE: If bpython sees an argument it does ' - 'not know, execution falls back to the ' - 'regular Python interpreter.')) + usage=_( + "Usage: %prog [options] [file [args]]\n" + "NOTE: If bpython sees an argument it does " + "not know, execution falls back to the " + "regular Python interpreter." + ) + ) # This is not sufficient if bpython gains its own -m support # (instead of falling back to Python itself for that). # That's probably fixable though, for example by having that # option swallow all remaining arguments in a callback. parser.disable_interspersed_args() - parser.add_option('--config', default=default_config_path(), - help=_('Use CONFIG instead of default config file.')) - parser.add_option('--interactive', '-i', action='store_true', - help=_('Drop to bpython shell after running file ' - 'instead of exiting.')) - parser.add_option('--quiet', '-q', action='store_true', - help=_("Don't flush the output to stdout.")) - parser.add_option('--version', '-V', action='store_true', - help=_('Print version and exit.')) + parser.add_option( + "--config", + default=default_config_path(), + help=_("Use CONFIG instead of default config file."), + ) + parser.add_option( + "--interactive", + "-i", + action="store_true", + help=_( + "Drop to bpython shell after running file " "instead of exiting." + ), + ) + parser.add_option( + "--quiet", + "-q", + action="store_true", + help=_("Don't flush the output to stdout."), + ) + parser.add_option( + "--version", + "-V", + action="store_true", + help=_("Print version and exit."), + ) if extras is not None: extras_group = OptionGroup(parser, extras[0], extras[1]) @@ -93,8 +115,10 @@ def parse(args, extras=None, ignore_stdin=False): if options.version: print(version_banner()) - print('(C) 2008-2016 Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al. ' - 'See AUTHORS for detail.') + print( + "(C) 2008-2016 Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al. " + "See AUTHORS for detail." + ) raise SystemExit if not ignore_stdin and not (sys.stdin.isatty() and sys.stdout.isatty()): @@ -113,13 +137,13 @@ def exec_code(interpreter, args): Helper to execute code in a given interpreter. args should be a [faked] sys.argv """ - with open(args[0], 'r') as sourcefile: + with open(args[0], "r") as sourcefile: source = sourcefile.read() old_argv, sys.argv = sys.argv, args sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) - mod = imp.new_module('__console__') - sys.modules['__console__'] = mod + mod = imp.new_module("__console__") + sys.modules["__console__"] = mod interpreter.locals = mod.__dict__ - interpreter.locals['__file__'] = args[0] - interpreter.runsource(source, args[0], 'exec') + interpreter.locals["__file__"] = args[0] + interpreter.runsource(source, args[0], "exec") sys.argv = old_argv diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 578a10bbb..53e1ea0f9 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -42,36 +42,84 @@ from .line import LinePart from ._py3compat import py3, try_decode from .lazyre import LazyReCompile -from .simpleeval import (safe_eval, evaluate_current_expression, - EvaluationError) +from .simpleeval import safe_eval, evaluate_current_expression, EvaluationError if not py3: from types import InstanceType, ClassType # Autocomplete modes -SIMPLE = 'simple' -SUBSTRING = 'substring' -FUZZY = 'fuzzy' +SIMPLE = "simple" +SUBSTRING = "substring" +FUZZY = "fuzzy" ALL_MODES = (SIMPLE, SUBSTRING, FUZZY) -MAGIC_METHODS = tuple("__%s__" % s for s in ( - "init", "repr", "str", "lt", "le", "eq", "ne", "gt", "ge", "cmp", "hash", - "nonzero", "unicode", "getattr", "setattr", "get", "set", "call", "len", - "getitem", "setitem", "iter", "reversed", "contains", "add", "sub", "mul", - "floordiv", "mod", "divmod", "pow", "lshift", "rshift", "and", "xor", "or", - "div", "truediv", "neg", "pos", "abs", "invert", "complex", "int", "float", - "oct", "hex", "index", "coerce", "enter", "exit")) +MAGIC_METHODS = tuple( + "__%s__" % s + for s in ( + "init", + "repr", + "str", + "lt", + "le", + "eq", + "ne", + "gt", + "ge", + "cmp", + "hash", + "nonzero", + "unicode", + "getattr", + "setattr", + "get", + "set", + "call", + "len", + "getitem", + "setitem", + "iter", + "reversed", + "contains", + "add", + "sub", + "mul", + "floordiv", + "mod", + "divmod", + "pow", + "lshift", + "rshift", + "and", + "xor", + "or", + "div", + "truediv", + "neg", + "pos", + "abs", + "invert", + "complex", + "int", + "float", + "oct", + "hex", + "index", + "coerce", + "enter", + "exit", + ) +) if py3: KEYWORDS = frozenset(keyword.kwlist) else: - KEYWORDS = frozenset(name.decode('ascii') for name in keyword.kwlist) + KEYWORDS = frozenset(name.decode("ascii") for name in keyword.kwlist) def after_last_dot(name): - return name.rstrip('.').rsplit('.')[-1] + return name.rstrip(".").rsplit(".")[-1] def few_enough_underscores(current, match): @@ -81,11 +129,11 @@ def few_enough_underscores(current, match): if current is __, True regardless of match otherwise True if match does not start with any underscore """ - if current.startswith('__'): + if current.startswith("__"): return True - elif current.startswith('_') and not match.startswith('__'): + elif current.startswith("_") and not match.startswith("__"): return True - elif match.startswith('_'): + elif match.startswith("_"): return False else: return True @@ -100,14 +148,14 @@ def method_match_substring(word, size, text): def method_match_fuzzy(word, size, text): - s = r'.*%s.*' % '.*'.join(list(text)) + s = r".*%s.*" % ".*".join(list(text)) return re.search(s, word) MODES_MAP = { SIMPLE: method_match_simple, SUBSTRING: method_match_substring, - FUZZY: method_match_fuzzy + FUZZY: method_match_fuzzy, } @@ -151,7 +199,7 @@ def substitute(self, cursor_offset, line, match): """Returns a cursor offset and line with match swapped in""" lpart = self.locate(cursor_offset, line) offset = lpart.start + len(match) - changed_line = line[:lpart.start] + match + line[lpart.end:] + changed_line = line[: lpart.start] + match + line[lpart.end :] return offset, changed_line @property @@ -167,7 +215,8 @@ class CumulativeCompleter(BaseCompletionType): def __init__(self, completers, mode=SIMPLE): if not completers: raise ValueError( - "CumulativeCompleter requires at least one completer") + "CumulativeCompleter requires at least one completer" + ) self._completers = completers super(CumulativeCompleter, self).__init__(True, mode) @@ -182,9 +231,9 @@ def matches(self, cursor_offset, line, **kwargs): return_value = None all_matches = set() for completer in self._completers: - matches = completer.matches(cursor_offset=cursor_offset, - line=line, - **kwargs) + matches = completer.matches( + cursor_offset=cursor_offset, line=line, **kwargs + ) if matches is not None: all_matches.update(matches) return_value = all_matches @@ -193,7 +242,6 @@ def matches(self, cursor_offset, line, **kwargs): class ImportCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs): return importcompletion.complete(cursor_offset, line) @@ -205,17 +253,19 @@ def format(self, word): class FilenameCompletion(BaseCompletionType): - def __init__(self, mode=SIMPLE): super(FilenameCompletion, self).__init__(False, mode) if py3: + def safe_glob(self, pathname): - return glob.iglob(glob.escape(pathname) + '*') + return glob.iglob(glob.escape(pathname) + "*") + else: + def safe_glob(self, pathname): try: - return glob.glob(pathname + '*') + return glob.glob(pathname + "*") except re.error: # see #491 return tuple() @@ -230,8 +280,8 @@ def matches(self, cursor_offset, line, **kwargs): for filename in self.safe_glob(os.path.expanduser(cs.word)): if os.path.isdir(filename): filename += os.path.sep - if cs.word.startswith('~'): - filename = username + filename[len(user_dir):] + if cs.word.startswith("~"): + filename = username + filename[len(user_dir) :] matches.add(filename) return matches @@ -241,7 +291,7 @@ def locate(self, current_offset, line): def format(self, filename): filename.rstrip(os.sep).rsplit(os.sep)[-1] if os.sep in filename[:-1]: - return filename[filename.rindex(os.sep, 0, -1) + 1:] + return filename[filename.rindex(os.sep, 0, -1) + 1 :] else: return filename @@ -251,9 +301,9 @@ class AttrCompletion(BaseCompletionType): attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") def matches(self, cursor_offset, line, **kwargs): - if 'locals_' not in kwargs: + if "locals_" not in kwargs: return None - locals_ = kwargs['locals_'] + locals_ = kwargs["locals_"] r = self.locate(cursor_offset, line) if r is None: @@ -262,19 +312,23 @@ def matches(self, cursor_offset, line, **kwargs): if locals_ is None: # TODO add a note about why locals_ = __main__.__dict__ - assert '.' in r.word + assert "." in r.word for i in range(1, len(r.word) + 1): - if r.word[-i] == '[': + if r.word[-i] == "[": i -= 1 break methodtext = r.word[-i:] - matches = set(''.join([r.word[:-i], m]) - for m in self.attr_matches(methodtext, locals_)) + matches = set( + "".join([r.word[:-i], m]) + for m in self.attr_matches(methodtext, locals_) + ) - return set(m for m in matches - if few_enough_underscores(r.word.split('.')[-1], - m.split('.')[-1])) + return set( + m + for m in matches + if few_enough_underscores(r.word.split(".")[-1], m.split(".")[-1]) + ) def locate(self, current_offset, line): return lineparts.current_dotted_attribute(current_offset, line) @@ -311,18 +365,18 @@ def attr_lookup(self, obj, expr, attr): be wrapped in a safe try/finally block in case anything bad happens to restore the original __getattribute__ method.""" words = self.list_attributes(obj) - if hasattr(obj, '__class__'): - words.append('__class__') + if hasattr(obj, "__class__"): + words.append("__class__") words = words + rlcompleter.get_class_members(obj.__class__) if not isinstance(obj.__class__, abc.ABCMeta): try: - words.remove('__abstractmethods__') + words.remove("__abstractmethods__") except ValueError: pass if not py3 and isinstance(obj, (InstanceType, ClassType)): # Account for the __dict__ in an old-style class. - words.append('__dict__') + words.append("__dict__") matches = [] n = len(attr) @@ -332,9 +386,12 @@ def attr_lookup(self, obj, expr, attr): return matches if py3: + def list_attributes(self, obj): return dir(obj) + else: + def list_attributes(self, obj): if isinstance(obj, InstanceType): try: @@ -343,17 +400,16 @@ def list_attributes(self, obj): # This is a case where we can not prevent user code from # running. We return a default list attributes on error # instead. (#536) - return ['__doc__', '__module__'] + return ["__doc__", "__module__"] else: return dir(obj) class DictKeyCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs): - if 'locals_' not in kwargs: + if "locals_" not in kwargs: return None - locals_ = kwargs['locals_'] + locals_ = kwargs["locals_"] r = self.locate(cursor_offset, line) if r is None: @@ -364,8 +420,11 @@ def matches(self, cursor_offset, line, **kwargs): except EvaluationError: return None if isinstance(obj, dict) and obj.keys(): - matches = set("{0!r}]".format(k) for k in obj.keys() - if repr(k).startswith(r.word)) + matches = set( + "{0!r}]".format(k) + for k in obj.keys() + if repr(k).startswith(r.word) + ) return matches if matches else None else: return None @@ -378,16 +437,15 @@ def format(self, match): class MagicMethodCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs): - if 'current_block' not in kwargs: + if "current_block" not in kwargs: return None - current_block = kwargs['current_block'] + current_block = kwargs["current_block"] r = self.locate(cursor_offset, line) if r is None: return None - if 'class' not in current_block: + if "class" not in current_block: return None return set(name for name in MAGIC_METHODS if name.startswith(r.word)) @@ -396,15 +454,14 @@ def locate(self, current_offset, line): class GlobalCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs): """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace that match. """ - if 'locals_' not in kwargs: + if "locals_" not in kwargs: return None - locals_ = kwargs['locals_'] + locals_ = kwargs["locals_"] r = self.locate(cursor_offset, line) if r is None: @@ -417,12 +474,14 @@ def matches(self, cursor_offset, line, **kwargs): matches.add(word) for nspace in (builtins.__dict__, locals_): for word, val in iteritems(nspace): - word = try_decode(word, 'ascii') + word = try_decode(word, "ascii") # if identifier isn't ascii, don't complete (syntax error) if word is None: continue - if (self.method_match(word, n, r.word) and - word != "__builtins__"): + if ( + self.method_match(word, n, r.word) + and word != "__builtins__" + ): matches.add(_callable_postfix(val, word)) return matches if matches else None @@ -431,11 +490,10 @@ def locate(self, current_offset, line): class ParameterNameCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs): - if 'argspec' not in kwargs: + if "argspec" not in kwargs: return None - argspec = kwargs['argspec'] + argspec = kwargs["argspec"] if not argspec: return None @@ -443,12 +501,17 @@ def matches(self, cursor_offset, line, **kwargs): if r is None: return None if argspec: - matches = set(name + '=' for name in argspec[1][0] - if isinstance(name, string_types) and - name.startswith(r.word)) + matches = set( + name + "=" + for name in argspec[1][0] + if isinstance(name, string_types) and name.startswith(r.word) + ) if py3: - matches.update(name + '=' for name in argspec[1][4] - if name.startswith(r.word)) + matches.update( + name + "=" + for name in argspec[1][4] + if name.startswith(r.word) + ) return matches if matches else None def locate(self, current_offset, line): @@ -461,9 +524,9 @@ def locate(self, current_offset, line): return lineparts.current_expression_attribute(current_offset, line) def matches(self, cursor_offset, line, **kwargs): - if 'locals_' not in kwargs: + if "locals_" not in kwargs: return None - locals_ = kwargs['locals_'] + locals_ = kwargs["locals_"] if locals_ is None: locals_ = __main__.__dict__ @@ -476,7 +539,7 @@ def matches(self, cursor_offset, line, **kwargs): return set() with inspection.AttrCleaner(obj): # strips leading dot - matches = [m[1:] for m in self.attr_lookup(obj, '', attr.word)] + matches = [m[1:] for m in self.attr_lookup(obj, "", attr.word)] return set(m for m in matches if few_enough_underscores(attr.word, m)) @@ -484,24 +547,28 @@ def matches(self, cursor_offset, line, **kwargs): try: import jedi except ImportError: + class MultilineJediCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): return None + + else: - class JediCompletion(BaseCompletionType): + class JediCompletion(BaseCompletionType): def matches(self, cursor_offset, line, **kwargs): - if 'history' not in kwargs: + if "history" not in kwargs: return None - history = kwargs['history'] + history = kwargs["history"] if not lineparts.current_word(cursor_offset, line): return None - history = '\n'.join(history) + '\n' + line + history = "\n".join(history) + "\n" + line try: - script = jedi.Script(history, len(history.splitlines()), - cursor_offset, 'fake.py') + script = jedi.Script( + history, len(history.splitlines()), cursor_offset, "fake.py" + ) completions = script.completions() except (jedi.NotFoundError, IndexError, KeyError): # IndexError for #483 @@ -516,11 +583,12 @@ def matches(self, cursor_offset, line, **kwargs): self._orig_start = None return None - first_letter = line[self._orig_start:self._orig_start + 1] + first_letter = line[self._orig_start : self._orig_start + 1] - matches = [try_decode(c.name, 'ascii') for c in completions] - if any(not m.lower().startswith(matches[0][0].lower()) - for m in matches): + matches = [try_decode(c.name, "ascii") for c in completions] + if any( + not m.lower().startswith(matches[0][0].lower()) for m in matches + ): # Too general - giving completions starting with multiple # letters return None @@ -535,17 +603,19 @@ def locate(self, cursor_offset, line): class MultilineJediCompletion(JediCompletion): def matches(self, cursor_offset, line, **kwargs): - if 'current_block' not in kwargs or 'history' not in kwargs: + if "current_block" not in kwargs or "history" not in kwargs: return None - current_block = kwargs['current_block'] - history = kwargs['history'] - - if '\n' in current_block: - assert cursor_offset <= len(line), "%r %r" % (cursor_offset, - line) - results = super(MultilineJediCompletion, - self).matches(cursor_offset, line, - history=history) + current_block = kwargs["current_block"] + history = kwargs["history"] + + if "\n" in current_block: + assert cursor_offset <= len(line), "%r %r" % ( + cursor_offset, + line, + ) + results = super(MultilineJediCompletion, self).matches( + cursor_offset, line, history=history + ) return results else: return None @@ -575,8 +645,10 @@ def get_completer(completers, cursor_offset, line, **kwargs): # Instead of crashing the UI, log exceptions from autocompleters. logger = logging.getLogger(__name__) logger.debug( - 'Completer {} failed with unhandled exception: {}'.format( - completer, e)) + "Completer {} failed with unhandled exception: {}".format( + completer, e + ) + ) continue if matches is not None: return sorted(matches), (completer if matches else None) @@ -591,9 +663,10 @@ def get_default_completer(mode=SIMPLE): FilenameCompletion(mode=mode), MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), - CumulativeCompleter((GlobalCompletion(mode=mode), - ParameterNameCompletion(mode=mode)), - mode=mode), + CumulativeCompleter( + (GlobalCompletion(mode=mode), ParameterNameCompletion(mode=mode)), + mode=mode, + ), AttrCompletion(mode=mode), ExpressionAttributeCompletion(mode=mode), ) @@ -601,13 +674,12 @@ def get_default_completer(mode=SIMPLE): def get_completer_bpython(cursor_offset, line, **kwargs): """""" - return get_completer(get_default_completer(), - cursor_offset, line, **kwargs) + return get_completer(get_default_completer(), cursor_offset, line, **kwargs) def _callable_postfix(value, word): """rlcompleter's _callable_postfix done right.""" with inspection.AttrCleaner(value): if inspection.is_callable(value): - word += '(' + word += "(" return word diff --git a/bpython/cli.py b/bpython/cli.py index 14415a3a6..589f49045 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -51,7 +51,8 @@ import functools import struct -if platform.system() != 'Windows': + +if platform.system() != "Windows": import signal # Windows does not have job control import termios # Windows uses curses import fcntl # Windows uses curses @@ -105,7 +106,7 @@ def calculate_screen_lines(tokens, width, cursor=0): lines = 1 pos = cursor for (token, value) in tokens: - if token is Token.Text and value == '\n': + if token is Token.Text and value == "\n": lines += 1 else: pos += len(value) @@ -122,6 +123,7 @@ def newfunc(self, *args, **kwargs): return func(self, *args, **kwargs) else: return getattr(self.get_dest(), newfunc.__name__)(*args, **kwargs) + return newfunc @@ -184,17 +186,17 @@ def readline(self, size=-1): someone does something weird to stop it from blowing up.""" if not size: - return '' + return "" elif self.buffer: buffer = self.buffer.pop(0) else: - buffer = '' + buffer = "" curses.raw(True) try: - while not buffer.endswith(('\n', '\r')): + while not buffer.endswith(("\n", "\r")): key = self.interface.get_key() - if key in [curses.erasechar(), 'KEY_BACKSPACE']: + if key in [curses.erasechar(), "KEY_BACKSPACE"]: y, x = self.interface.scr.getyx() if buffer: self.interface.scr.delch(y, x - 1) @@ -202,9 +204,10 @@ def readline(self, size=-1): continue elif key == chr(4) and not buffer: # C-d - return '' - elif (key not in ('\n', '\r') and - (len(key) > 1 or unicodedata.category(key) == 'Cc')): + return "" + elif key not in ("\n", "\r") and ( + len(key) > 1 or unicodedata.category(key) == "Cc" + ): continue sys.stdout.write(key) # Include the \n in the buffer - raw_input() seems to deal with trailing @@ -226,7 +229,7 @@ def readline(self, size=-1): def read(self, size=None): if size == 0: - return '' + return "" data = list() while size is None or size > 0: @@ -237,10 +240,11 @@ def read(self, size=None): size -= len(line) data.append(line) - return ''.join(data) + return "".join(data) def readlines(self, size=-1): - return list(iter(self.readline, '')) + return list(iter(self.readline, "")) + # TODO: # @@ -270,36 +274,37 @@ def make_colors(config): # blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default: c = { - 'k': 0, - 'r': 1, - 'g': 2, - 'y': 3, - 'b': 4, - 'm': 5, - 'c': 6, - 'w': 7, - 'd': -1, + "k": 0, + "r": 1, + "g": 2, + "y": 3, + "b": 4, + "m": 5, + "c": 6, + "w": 7, + "d": -1, } - if platform.system() == 'Windows': - c = dict(list(c.items()) + - [ - ('K', 8), - ('R', 9), - ('G', 10), - ('Y', 11), - ('B', 12), - ('M', 13), - ('C', 14), - ('W', 15), - ] + if platform.system() == "Windows": + c = dict( + list(c.items()) + + [ + ("K", 8), + ("R", 9), + ("G", 10), + ("Y", 11), + ("B", 12), + ("M", 13), + ("C", 14), + ("W", 15), + ] ) for i in range(63): if i > 7: j = i // 8 else: - j = c[config.color_scheme['background']] + j = c[config.color_scheme["background"]] curses.init_pair(i + 1, i % 8, j) return c @@ -316,7 +321,7 @@ def confirm(self, q): except ValueError: return False - return reply.lower() in (_('y'), _('yes')) + return reply.lower() in (_("y"), _("yes")) def notify(self, s, n=10, wait_for_keypress=False): return self.statusbar.message(s, n) @@ -326,22 +331,21 @@ def file_prompt(self, s): class CLIRepl(repl.Repl): - def __init__(self, scr, interp, statusbar, config, idle=None): super(CLIRepl, self).__init__(interp, config) self.interp.writetb = self.writetb self.scr = scr - self.stdout_hist = '' # native str (bytes in Py2, unicode in Py3) - self.list_win = newwin(get_colpair(config, 'background'), 1, 1, 1, 1) + self.stdout_hist = "" # native str (bytes in Py2, unicode in Py3) + self.list_win = newwin(get_colpair(config, "background"), 1, 1, 1, 1) self.cpos = 0 self.do_exit = False self.exit_value = () - self.f_string = '' + self.f_string = "" self.idle = idle self.in_hist = False self.paste_mode = False self.last_key_press = time.time() - self.s = '' + self.s = "" self.statusbar = statusbar self.formatter = BPythonFormatter(config.color_scheme) self.interact = CLIInteraction(self.config, statusbar=self.statusbar) @@ -354,8 +358,13 @@ def _get_cursor_offset(self): def _set_cursor_offset(self, offset): self.cpos = len(self.s) - offset - cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, - "The cursor offset from the beginning of the line") + + cursor_offset = property( + _get_cursor_offset, + _set_cursor_offset, + None, + "The cursor offset from the beginning of the line", + ) def addstr(self, s): """Add a string to the current input line and figure out @@ -365,7 +374,7 @@ def addstr(self, s): self.s += s else: l = len(self.s) - self.s = self.s[:l - self.cpos] + s + self.s[l - self.cpos:] + self.s = self.s[: l - self.cpos] + s + self.s[l - self.cpos :] self.complete() @@ -403,7 +412,7 @@ def bs(self, delete_tabs=True): self.s = self.s[:-n] else: - self.s = self.s[:-self.cpos - 1] + self.s[-self.cpos:] + self.s = self.s[: -self.cpos - 1] + self.s[-self.cpos :] self.print_line(self.s, clr=True) @@ -414,22 +423,24 @@ def bs_word(self): pos = len(self.s) - self.cpos - 1 deleted = [] # First we delete any space to the left of the cursor. - while pos >= 0 and self.s[pos] == ' ': + while pos >= 0 and self.s[pos] == " ": deleted.append(self.s[pos]) pos -= self.bs() # Then we delete a full word. - while pos >= 0 and self.s[pos] != ' ': + while pos >= 0 and self.s[pos] != " ": deleted.append(self.s[pos]) pos -= self.bs() - return ''.join(reversed(deleted)) + return "".join(reversed(deleted)) def check(self): """Check if paste mode should still be active and, if not, deactivate it and force syntax highlighting.""" - if (self.paste_mode - and time.time() - self.last_key_press > self.config.paste_time): + if ( + self.paste_mode + and time.time() - self.last_key_press > self.config.paste_time + ): self.paste_mode = False self.print_line(self.s) @@ -438,7 +449,7 @@ def clear_current_line(self): used to prevent autoindentation from occurring after a traceback.""" repl.Repl.clear_current_line(self) - self.s = '' + self.s = "" def clear_wrapped_lines(self): """Clear the wrapped lines of the current input.""" @@ -462,9 +473,12 @@ def complete(self, tab=False): list_win_visible = repl.Repl.complete(self, tab) if list_win_visible: try: - self.show_list(self.matches_iter.matches, self.arg_pos, - topline=self.funcprops, - formatter=self.matches_iter.completer.format) + self.show_list( + self.matches_iter.matches, + self.arg_pos, + topline=self.funcprops, + formatter=self.matches_iter.completer.format, + ) except curses.error: # XXX: This is a massive hack, it will go away when I get # cusswords into a good enough state that we can start @@ -481,9 +495,9 @@ def clrtobol(self): self.clear_wrapped_lines() if not self.cpos: - self.s = '' + self.s = "" else: - self.s = self.s[-self.cpos:] + self.s = self.s[-self.cpos :] self.print_line(self.s, clr=True) self.scr.redrawwin() @@ -494,13 +508,18 @@ def _get_current_line(self): def _set_current_line(self, line): self.s = line - current_line = property(_get_current_line, _set_current_line, None, - "The characters of the current line") + + current_line = property( + _get_current_line, + _set_current_line, + None, + "The characters of the current line", + ) def cut_to_buffer(self): """Clear from cursor to end of line, placing into cut buffer""" - self.cut_buffer = self.s[-self.cpos:] - self.s = self.s[:-self.cpos] + self.cut_buffer = self.s[-self.cpos :] + self.s = self.s[: -self.cpos] self.cpos = 0 self.print_line(self.s, clr=True) self.scr.redrawwin() @@ -522,29 +541,29 @@ def echo(self, s, redraw=True): if not py3 and isinstance(s, unicode): s = s.encode(getpreferredencoding()) - a = get_colpair(self.config, 'output') - if '\x01' in s: - rx = re.search('\x01([A-Za-z])([A-Za-z]?)', s) + a = get_colpair(self.config, "output") + if "\x01" in s: + rx = re.search("\x01([A-Za-z])([A-Za-z]?)", s) if rx: fg = rx.groups()[0] bg = rx.groups()[1] col_num = self._C[fg.lower()] - if bg and bg != 'I': + if bg and bg != "I": col_num *= self._C[bg.lower()] a = curses.color_pair(int(col_num) + 1) - if bg == 'I': + if bg == "I": a = a | curses.A_REVERSE - s = re.sub('\x01[A-Za-z][A-Za-z]?', '', s) + s = re.sub("\x01[A-Za-z][A-Za-z]?", "", s) if fg.isupper(): a = a | curses.A_BOLD - s = s.replace('\x03', '') - s = s.replace('\x01', '') + s = s.replace("\x03", "") + s = s.replace("\x01", "") # Replace NUL bytes, as addstr raises an exception otherwise - s = s.replace('\0', '') + s = s.replace("\0", "") # Replace \r\n bytes, as addstr remove the current line otherwise - s = s.replace('\r\n', '\n') + s = s.replace("\r\n", "\n") self.scr.addstr(s, a) @@ -608,7 +627,7 @@ def search(self): self.print_line(self.s, clr=True) def get_key(self): - key = '' + key = "" while True: try: key += self.scr.getkey() @@ -617,7 +636,7 @@ def get_key(self): # encoded string in Python 3 as well, but of # type str instead of bytes, hence convert it to # bytes first and decode then - key = key.encode('latin-1').decode(getpreferredencoding()) + key = key.encode("latin-1").decode(getpreferredencoding()) else: key = key.decode(getpreferredencoding()) self.scr.nodelay(False) @@ -637,7 +656,7 @@ def get_key(self): if key: return key else: - if key != '\x00': + if key != "\x00": t = time.time() self.paste_mode = ( t - self.last_key_press <= self.config.paste_time @@ -645,7 +664,7 @@ def get_key(self): self.last_key_press = t return key else: - key = '' + key = "" finally: if self.idle: self.idle(self) @@ -659,13 +678,13 @@ def get_line(self): which returns None if Enter is pressed (that means "Return", idiot).""" - self.s = '' + self.s = "" self.rl_history.reset() self.iy, self.ix = self.scr.getyx() if not self.paste_mode: for _ in range(self.next_indentation()): - self.p_key('\t') + self.p_key("\t") self.cpos = 0 @@ -717,16 +736,17 @@ def mkargspec(self, topline, in_arg, down): self.list_win.resize(3, max_w) h, w = self.list_win.getmaxyx() - self.list_win.addstr('\n ') - self.list_win.addstr(fn, - get_colpair(self.config, 'name') | curses.A_BOLD) - self.list_win.addstr(': (', get_colpair(self.config, 'name')) + self.list_win.addstr("\n ") + self.list_win.addstr( + fn, get_colpair(self.config, "name") | curses.A_BOLD + ) + self.list_win.addstr(": (", get_colpair(self.config, "name")) maxh = self.scr.getmaxyx()[0] if is_bound_method and isinstance(in_arg, int): in_arg += 1 - punctuation_colpair = get_colpair(self.config, 'punctuation') + punctuation_colpair = get_colpair(self.config, "punctuation") for k, i in enumerate(args): y, x = self.list_win.getyx() @@ -748,12 +768,12 @@ def mkargspec(self, topline, in_arg, down): else: break r += 1 - self.list_win.addstr('\n\t') + self.list_win.addstr("\n\t") - if str(i) == 'self' and k == 0: - color = get_colpair(self.config, 'name') + if str(i) == "self" and k == 0: + color = get_colpair(self.config, "name") else: - color = get_colpair(self.config, 'token') + color = get_colpair(self.config, "token") if k == in_arg or i == in_arg: color |= curses.A_BOLD @@ -766,41 +786,44 @@ def mkargspec(self, topline, in_arg, down): else: self.list_win.addstr(str(i), color) if kw is not None: - self.list_win.addstr('=', punctuation_colpair) - self.list_win.addstr(kw, get_colpair(self.config, 'token')) + self.list_win.addstr("=", punctuation_colpair) + self.list_win.addstr(kw, get_colpair(self.config, "token")) if k != len(args) - 1: - self.list_win.addstr(', ', punctuation_colpair) + self.list_win.addstr(", ", punctuation_colpair) if _args: if args: - self.list_win.addstr(', ', punctuation_colpair) - self.list_win.addstr('*%s' % (_args, ), - get_colpair(self.config, 'token')) + self.list_win.addstr(", ", punctuation_colpair) + self.list_win.addstr( + "*%s" % (_args,), get_colpair(self.config, "token") + ) if py3 and kwonly: if not _args: if args: - self.list_win.addstr(', ', punctuation_colpair) - self.list_win.addstr('*', punctuation_colpair) + self.list_win.addstr(", ", punctuation_colpair) + self.list_win.addstr("*", punctuation_colpair) marker = object() for arg in kwonly: - self.list_win.addstr(', ', punctuation_colpair) - color = get_colpair(self.config, 'token') + self.list_win.addstr(", ", punctuation_colpair) + color = get_colpair(self.config, "token") if arg == in_arg: color |= curses.A_BOLD self.list_win.addstr(arg, color) default = kwonly_defaults.get(arg, marker) if default is not marker: - self.list_win.addstr('=', punctuation_colpair) - self.list_win.addstr(repr(default), - get_colpair(self.config, 'token')) + self.list_win.addstr("=", punctuation_colpair) + self.list_win.addstr( + repr(default), get_colpair(self.config, "token") + ) if _kwargs: if args or _args or (py3 and kwonly): - self.list_win.addstr(', ', punctuation_colpair) - self.list_win.addstr('**%s' % (_kwargs, ), - get_colpair(self.config, 'token')) - self.list_win.addstr(')', punctuation_colpair) + self.list_win.addstr(", ", punctuation_colpair) + self.list_win.addstr( + "**%s" % (_kwargs,), get_colpair(self.config, "token") + ) + self.list_win.addstr(")", punctuation_colpair) return r @@ -841,11 +864,11 @@ def p_key(self, key): """Process a keypress""" if key is None: - return '' + return "" config = self.config - if platform.system() == 'Windows': + if platform.system() == "Windows": C_BACK = chr(127) BACKSP = chr(8) else: @@ -854,178 +877,179 @@ def p_key(self, key): if key == C_BACK: # C-Backspace (on my computer anyway!) self.clrtobol() - key = '\n' + key = "\n" # Don't return; let it get handled if key == chr(27): # Escape Key - return '' + return "" - if key in (BACKSP, 'KEY_BACKSPACE'): + if key in (BACKSP, "KEY_BACKSPACE"): self.bs() self.complete() - return '' + return "" elif key in key_dispatch[config.delete_key] and not self.s: # Delete on empty line exits self.do_exit = True return None - elif key in ('KEY_DC', ) + key_dispatch[config.delete_key]: + elif key in ("KEY_DC",) + key_dispatch[config.delete_key]: self.delete() self.complete() # Redraw (as there might have been highlighted parens) self.print_line(self.s) - return '' + return "" elif key in key_dispatch[config.undo_key]: # C-r n = self.prompt_undo() if n > 0: self.undo(n=n) - return '' + return "" elif key in key_dispatch[config.search_key]: self.search() - return '' + return "" - elif key in ('KEY_UP', ) + key_dispatch[config.up_one_line_key]: + elif key in ("KEY_UP",) + key_dispatch[config.up_one_line_key]: # Cursor Up/C-p self.back() - return '' + return "" - elif key in ('KEY_DOWN', ) + key_dispatch[config.down_one_line_key]: + elif key in ("KEY_DOWN",) + key_dispatch[config.down_one_line_key]: # Cursor Down/C-n self.fwd() - return '' + return "" - elif key in ("KEY_LEFT", ' ^B', chr(2)): # Cursor Left or ^B + elif key in ("KEY_LEFT", " ^B", chr(2)): # Cursor Left or ^B self.mvc(1) # Redraw (as there might have been highlighted parens) self.print_line(self.s) - elif key in ("KEY_RIGHT", '^F', chr(6)): # Cursor Right or ^F + elif key in ("KEY_RIGHT", "^F", chr(6)): # Cursor Right or ^F self.mvc(-1) # Redraw (as there might have been highlighted parens) self.print_line(self.s) - elif key in ("KEY_HOME", '^A', chr(1)): # home or ^A + elif key in ("KEY_HOME", "^A", chr(1)): # home or ^A self.home() # Redraw (as there might have been highlighted parens) self.print_line(self.s) - elif key in ("KEY_END", '^E', chr(5)): # end or ^E + elif key in ("KEY_END", "^E", chr(5)): # end or ^E self.end() # Redraw (as there might have been highlighted parens) self.print_line(self.s) - elif key in ("KEY_NPAGE", '\T'): # page_down or \T + elif key in ("KEY_NPAGE", "\T"): # page_down or \T self.hend() self.print_line(self.s) - elif key in ("KEY_PPAGE", '\S'): # page_up or \S + elif key in ("KEY_PPAGE", "\S"): # page_up or \S self.hbegin() self.print_line(self.s) elif key in key_dispatch[config.cut_to_buffer_key]: # cut to buffer self.cut_to_buffer() - return '' + return "" elif key in key_dispatch[config.yank_from_buffer_key]: # yank from buffer self.yank_from_buffer() - return '' + return "" elif key in key_dispatch[config.clear_word_key]: self.cut_buffer = self.bs_word() self.complete() - return '' + return "" elif key in key_dispatch[config.clear_line_key]: self.clrtobol() - return '' + return "" elif key in key_dispatch[config.clear_screen_key]: self.s_hist = [self.s_hist[-1]] self.highlighted_paren = None self.redraw() - return '' + return "" elif key in key_dispatch[config.exit_key]: if not self.s: self.do_exit = True return None else: - return '' + return "" elif key in key_dispatch[config.save_key]: self.write2file() - return '' + return "" elif key in key_dispatch[config.pastebin_key]: self.pastebin() - return '' + return "" elif key in key_dispatch[config.copy_clipboard_key]: self.copy2clipboard() - return '' + return "" elif key in key_dispatch[config.last_output_key]: - page(self.stdout_hist[self.prev_block_finished:-4]) - return '' + page(self.stdout_hist[self.prev_block_finished : -4]) + return "" elif key in key_dispatch[config.show_source_key]: try: source = self.get_source_of_current_name() except repl.SourceNotFound as e: - self.statusbar.message('%s' % (e, )) + self.statusbar.message("%s" % (e,)) else: if config.highlight_show_source: - source = format(PythonLexer().get_tokens(source), - TerminalFormatter()) + source = format( + PythonLexer().get_tokens(source), TerminalFormatter() + ) page(source) - return '' + return "" - elif key in ('\n', '\r', 'PADENTER'): + elif key in ("\n", "\r", "PADENTER"): self.lf() return None - elif key == '\t': + elif key == "\t": return self.tab() - elif key == 'KEY_BTAB': + elif key == "KEY_BTAB": return self.tab(back=True) elif key in key_dispatch[config.suspend_key]: - if platform.system() != 'Windows': + if platform.system() != "Windows": self.suspend() - return '' + return "" else: self.do_exit = True return None - elif key == '\x18': + elif key == "\x18": return self.send_current_line_to_editor() - elif key == '\x03': + elif key == "\x03": raise KeyboardInterrupt() - elif key[0:3] == 'PAD' and not key in ('PAD0', 'PADSTOP'): + elif key[0:3] == "PAD" and not key in ("PAD0", "PADSTOP"): pad_keys = { - 'PADMINUS': '-', - 'PADPLUS': '+', - 'PADSLASH': '/', - 'PADSTAR': '*', + "PADMINUS": "-", + "PADPLUS": "+", + "PADSLASH": "/", + "PADSTAR": "*", } try: self.addstr(pad_keys[key]) self.print_line(self.s) except KeyError: - return '' - elif len(key) == 1 and not unicodedata.category(key) == 'Cc': + return "" + elif len(key) == 1 and not unicodedata.category(key) == "Cc": self.addstr(key) self.print_line(self.s) else: - return '' + return "" return True @@ -1056,8 +1080,8 @@ def print_line(self, s, clr=False, newline=False): self.scr.refresh() if o: - for t in o.split('\x04'): - self.echo(t.rstrip('\n')) + for t in o.split("\x04"): + self.echo(t.rstrip("\n")) if self.cpos: t = self.cpos @@ -1068,23 +1092,27 @@ def print_line(self, s, clr=False, newline=False): def prompt(self, more): """Show the appropriate Python prompt""" if not more: - self.echo("\x01%s\x03%s" % - (self.config.color_scheme['prompt'], self.ps1)) + self.echo( + "\x01%s\x03%s" % (self.config.color_scheme["prompt"], self.ps1) + ) if py3: self.stdout_hist += self.ps1 else: self.stdout_hist += self.ps1.encode(getpreferredencoding()) - self.s_hist.append('\x01%s\x03%s\x04' % - (self.config.color_scheme['prompt'], self.ps1)) + self.s_hist.append( + "\x01%s\x03%s\x04" + % (self.config.color_scheme["prompt"], self.ps1) + ) else: - prompt_more_color = self.config.color_scheme['prompt_more'] + prompt_more_color = self.config.color_scheme["prompt_more"] self.echo("\x01%s\x03%s" % (prompt_more_color, self.ps2)) if py3: self.stdout_hist += self.ps2 else: self.stdout_hist += self.ps2.encode(getpreferredencoding()) - self.s_hist.append('\x01%s\x03%s\x04' % - (prompt_more_color, self.ps2)) + self.s_hist.append( + "\x01%s\x03%s\x04" % (prompt_more_color, self.ps2) + ) def push(self, s, insert_into_history=True): # curses.raw(True) prevents C-c from causing a SIGINT @@ -1106,10 +1134,10 @@ def redraw(self): if not s: continue self.iy, self.ix = self.scr.getyx() - for i in s.split('\x04'): + for i in s.split("\x04"): self.echo(i, redraw=False) if k < len(self.s_hist) - 1: - self.scr.addstr('\n') + self.scr.addstr("\n") self.iy, self.ix = self.scr.getyx() self.print_line(self.s) self.scr.refresh() @@ -1126,18 +1154,18 @@ def repl(self): # Use our own helper function because Python's will use real stdin and # stdout instead of our wrapped - self.push('from bpython._internal import _help as help\n', False) + self.push("from bpython._internal import _help as help\n", False) self.iy, self.ix = self.scr.getyx() self.more = False while not self.do_exit: - self.f_string = '' + self.f_string = "" self.prompt(self.more) try: inp = self.get_line() except KeyboardInterrupt: - self.statusbar.message('KeyboardInterrupt') - self.scr.addstr('\n') + self.statusbar.message("KeyboardInterrupt") + self.scr.addstr("\n") self.scr.touchwin() self.scr.refresh() continue @@ -1149,14 +1177,14 @@ def repl(self): self.history.append(inp) self.s_hist[-1] += self.f_string if py3: - self.stdout_hist += inp + '\n' + self.stdout_hist += inp + "\n" else: - self.stdout_hist += inp.encode(getpreferredencoding()) + '\n' + self.stdout_hist += inp.encode(getpreferredencoding()) + "\n" stdout_position = len(self.stdout_hist) self.more = self.push(inp) if not self.more: self.prev_block_finished = stdout_position - self.s = '' + self.s = "" return self.exit_value def reprint_line(self, lineno, tokens): @@ -1175,10 +1203,11 @@ def reprint_line(self, lineno, tokens): if real_lineno < 0: return - self.scr.move(real_lineno, - len(self.ps1) if lineno == 0 else len(self.ps2)) + self.scr.move( + real_lineno, len(self.ps1) if lineno == 0 else len(self.ps2) + ) line = format(tokens, BPythonFormatter(self.config.color_scheme)) - for string in line.split('\x04'): + for string in line.split("\x04"): self.echo(string) def resize(self): @@ -1195,14 +1224,14 @@ def getstdout(self): """This method returns the 'spoofed' stdout buffer, for writing to a file or sending to a pastebin or whatever.""" - return self.stdout_hist + '\n' + return self.stdout_hist + "\n" def reevaluate(self): """Clear the buffer, redraw the screen and re-evaluate the history""" self.evaluating = True - self.stdout_hist = '' - self.f_string = '' + self.stdout_hist = "" + self.f_string = "" self.buffer = [] self.scr.erase() self.s_hist = [] @@ -1214,21 +1243,21 @@ def reevaluate(self): self.iy, self.ix = self.scr.getyx() for line in self.history: if py3: - self.stdout_hist += line + '\n' + self.stdout_hist += line + "\n" else: - self.stdout_hist += line.encode(getpreferredencoding()) + '\n' + self.stdout_hist += line.encode(getpreferredencoding()) + "\n" self.print_line(line) self.s_hist[-1] += self.f_string # I decided it was easier to just do this manually # than to make the print_line and history stuff more flexible. - self.scr.addstr('\n') + self.scr.addstr("\n") self.more = self.push(line) self.prompt(self.more) self.iy, self.ix = self.scr.getyx() self.cpos = 0 indent = repl.next_indentation(self.s, self.config.tab_length) - self.s = '' + self.s = "" self.scr.refresh() if self.buffer: @@ -1236,17 +1265,17 @@ def reevaluate(self): self.tab() self.evaluating = False - #map(self.push, self.history) + # map(self.push, self.history) # ^-- That's how simple this method was at first :( def write(self, s): """For overriding stdout defaults""" - if '\x04' in s: - for block in s.split('\x04'): + if "\x04" in s: + for block in s.split("\x04"): self.write(block) return - if s.rstrip() and '\x03' in s: - t = s.split('\x03')[1] + if s.rstrip() and "\x03" in s: + t = s.split("\x03")[1] else: t = s @@ -1261,7 +1290,9 @@ def write(self, s): self.echo(s) self.s_hist.append(s.rstrip()) - def show_list(self, items, arg_pos, topline=None, formatter=None, current_item=None): + def show_list( + self, items, arg_pos, topline=None, formatter=None, current_item=None + ): shared = Struct() shared.cols = 0 @@ -1269,7 +1300,7 @@ def show_list(self, items, arg_pos, topline=None, formatter=None, current_item=N shared.wl = 0 y, x = self.scr.getyx() h, w = self.scr.getmaxyx() - down = (y < h // 2) + down = y < h // 2 if down: max_h = h - y else: @@ -1307,16 +1338,16 @@ def lsize(): if items: # visible items (we'll append until we can't fit any more in) - v_items = [items[0][:max_w - 3]] + v_items = [items[0][: max_w - 3]] lsize() else: v_items = [] for i in items[1:]: - v_items.append(i[:max_w - 3]) + v_items.append(i[: max_w - 3]) if not lsize(): del v_items[-1] - v_items[-1] = '...' + v_items[-1] = "..." break rows = shared.rows @@ -1340,14 +1371,15 @@ def lsize(): w = t if height_offset and display_rows + 5 >= max_h: - del v_items[-(cols * (height_offset)):] + del v_items[-(cols * (height_offset)) :] if self.docstring is None: self.list_win.resize(rows + 2, w) else: - docstring = self.format_docstring(self.docstring, max_w - 2, - max_h - height_offset) - docstring_string = ''.join(docstring) + docstring = self.format_docstring( + self.docstring, max_w - 2, max_h - height_offset + ) + docstring_string = "".join(docstring) rows += len(docstring) self.list_win.resize(rows, max_w) @@ -1357,28 +1389,30 @@ def lsize(): self.list_win.mvwin(y - rows - 2, 0) if v_items: - self.list_win.addstr('\n ') + self.list_win.addstr("\n ") if not py3: encoding = getpreferredencoding() for ix, i in enumerate(v_items): - padding = (wl - len(i)) * ' ' + padding = (wl - len(i)) * " " if i == current_item: - color = get_colpair(self.config, 'operator') + color = get_colpair(self.config, "operator") else: - color = get_colpair(self.config, 'main') + color = get_colpair(self.config, "main") if not py3: i = i.encode(encoding) self.list_win.addstr(i + padding, color) - if ((cols == 1 or (ix and not (ix + 1) % cols)) - and ix + 1 < len(v_items)): - self.list_win.addstr('\n ') + if (cols == 1 or (ix and not (ix + 1) % cols)) and ix + 1 < len( + v_items + ): + self.list_win.addstr("\n ") if self.docstring is not None: if not py3 and isinstance(docstring_string, unicode): - docstring_string = docstring_string.encode(encoding, 'ignore') - self.list_win.addstr('\n' + docstring_string, - get_colpair(self.config, 'comment')) + docstring_string = docstring_string.encode(encoding, "ignore") + self.list_win.addstr( + "\n" + docstring_string, get_colpair(self.config, "comment") + ) # XXX: After all the trouble I had with sizing the list box (I'm not very good # at that type of thing) I decided to do this bit of tidying up here just to # make sure there's no unnecessary blank lines, it makes things look nicer. @@ -1388,7 +1422,7 @@ def lsize(): self.statusbar.win.touchwin() self.statusbar.win.noutrefresh() - self.list_win.attron(get_colpair(self.config, 'main')) + self.list_win.attron(get_colpair(self.config, "main")) self.list_win.border() self.scr.touchwin() self.scr.cursyncup() @@ -1412,7 +1446,7 @@ def size(self): def suspend(self): """Suspend the current process for shell job control.""" - if platform.system() != 'Windows': + if platform.system() != "Windows": curses.endwin() os.kill(os.getpid(), signal.SIGSTOP) @@ -1435,7 +1469,7 @@ def tab(self, back=False): if not num_spaces: num_spaces = self.config.tab_length - self.addstr(' ' * num_spaces) + self.addstr(" " * num_spaces) self.print_line(self.s) return True @@ -1458,13 +1492,17 @@ def tab(self, back=False): # 4. swap current word for a match list item elif self.matches_iter.matches: - current_match = back and self.matches_iter.previous() \ - or next(self.matches_iter) + current_match = ( + back and self.matches_iter.previous() or next(self.matches_iter) + ) try: - self.show_list(self.matches_iter.matches, self.arg_pos, - topline=self.funcprops, - formatter=self.matches_iter.completer.format, - current_item=current_match) + self.show_list( + self.matches_iter.matches, + self.arg_pos, + topline=self.funcprops, + formatter=self.matches_iter.completer.format, + current_item=current_match, + ) except curses.error: # XXX: This is a massive hack, it will go away when I get # cusswords into a good enough state that we can start @@ -1483,8 +1521,9 @@ def undo(self, n=1): def writetb(self, lines): for line in lines: - self.write('\x01%s\x03%s' % (self.config.color_scheme['error'], - line)) + self.write( + "\x01%s\x03%s" % (self.config.color_scheme["error"], line) + ) def yank_from_buffer(self): """Paste the text from the cut buffer at the current cursor location""" @@ -1492,28 +1531,28 @@ def yank_from_buffer(self): self.print_line(self.s, clr=True) def send_current_line_to_editor(self): - lines = self.send_to_external_editor(self.s).split('\n') - self.s = '' + lines = self.send_to_external_editor(self.s).split("\n") + self.s = "" self.print_line(self.s) while lines and not lines[-1]: lines.pop() if not lines: - return '' + return "" - self.f_string = '' + self.f_string = "" self.cpos = -1 # Set cursor position to -1 to prevent paren matching self.iy, self.ix = self.scr.getyx() self.evaluating = True for line in lines: if py3: - self.stdout_hist += line + '\n' + self.stdout_hist += line + "\n" else: - self.stdout_hist += line.encode(getpreferredencoding()) + '\n' + self.stdout_hist += line.encode(getpreferredencoding()) + "\n" self.history.append(line) self.print_line(line) self.s_hist[-1] += self.f_string - self.scr.addstr('\n') + self.scr.addstr("\n") self.more = self.push(line) self.prompt(self.more) self.iy, self.ix = self.scr.getyx() @@ -1521,7 +1560,7 @@ def send_current_line_to_editor(self): self.cpos = 0 indent = repl.next_indentation(self.s, self.config.tab_length) - self.s = '' + self.s = "" self.scr.refresh() if self.buffer: @@ -1530,7 +1569,7 @@ def send_current_line_to_editor(self): self.print_line(self.s) self.scr.redrawwin() - return '' + return "" class Statusbar(object): @@ -1564,7 +1603,7 @@ def __init__(self, scr, pwin, background, config, s=None, c=None): self.config = config - self.s = s or '' + self.s = s or "" self._s = self.s self.c = c self.timer = 0 @@ -1611,12 +1650,12 @@ def message(self, s, n=3): self.timer = time.time() + n self.settext(s) - def prompt(self, s=''): + def prompt(self, s=""): """Prompt the user for some input (with the optional prompt 's') and return the input text, then restore the statusbar to its original value.""" - self.settext(s or '? ', p=True) + self.settext(s or "? ", p=True) iy, ix = self.win.getyx() def bs(s): @@ -1628,7 +1667,7 @@ def bs(s): self.win.move(y, x - 1) return s - o = '' + o = "" while True: c = self.win.getch() @@ -1645,7 +1684,7 @@ def bs(s): # literal elif 0 < c < 127: c = chr(c) - self.win.addstr(c, get_colpair(self.config, 'prompt')) + self.win.addstr(c, get_colpair(self.config, "prompt")) o += c self.settext(self._s) @@ -1660,7 +1699,7 @@ def settext(self, s, c=None, p=False): self.win.erase() if len(s) >= self.w: - s = s[:self.w - 1] + s = s[: self.w - 1] self.s = s if c: @@ -1691,7 +1730,7 @@ def init_wins(scr, config): status bar at the bottom with some stuff in it)""" # TODO: Document better what stuff is on the status bar. - background = get_colpair(config, 'background') + background = get_colpair(config, "background") h, w = gethw() main_win = newwin(background, h - 1, w, 0, 0) @@ -1701,18 +1740,20 @@ def init_wins(scr, config): # problems that needed dirty hackery to fix. :) commands = ( - (_('Rewind'), config.undo_key), - (_('Save'), config.save_key), - (_('Pastebin'), config.pastebin_key), - (_('Pager'), config.last_output_key), - (_('Show Source'), config.show_source_key) + (_("Rewind"), config.undo_key), + (_("Save"), config.save_key), + (_("Pastebin"), config.pastebin_key), + (_("Pager"), config.last_output_key), + (_("Show Source"), config.show_source_key), ) - message = ' '.join('<%s> %s' % (key, command) for command, key in commands - if key) + message = " ".join( + "<%s> %s" % (key, command) for command, key in commands if key + ) - statusbar = Statusbar(scr, main_win, background, config, message, - get_colpair(config, 'main')) + statusbar = Statusbar( + scr, main_win, background, config, message, get_colpair(config, "main") + ) return main_win, statusbar @@ -1725,7 +1766,7 @@ def sigwinch(unused_scr): def sigcont(unused_scr): sigwinch(unused_scr) # Forces the redraw - curses.ungetch('\x00') + curses.ungetch("\x00") def gethw(): @@ -1744,10 +1785,10 @@ def gethw(): """ - if platform.system() != 'Windows': + if platform.system() != "Windows": h, w = struct.unpack( - "hhhh", - fcntl.ioctl(sys.__stdout__, termios.TIOCGWINSZ, "\000" * 8))[0:2] + "hhhh", fcntl.ioctl(sys.__stdout__, termios.TIOCGWINSZ, "\000" * 8) + )[0:2] else: from ctypes import windll, create_string_buffer @@ -1760,8 +1801,19 @@ def gethw(): res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) if res: - (bufx, bufy, curx, cury, wattr, - left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) + ( + bufx, + bufy, + curx, + cury, + wattr, + left, + top, + right, + bottom, + maxx, + maxy, + ) = struct.unpack("hhhhHhhhhhh", csbi.raw) sizex = right - left + 1 sizey = bottom - top + 1 else: @@ -1787,7 +1839,7 @@ def idle(caller): if key != -1: curses.ungetch(key) else: - curses.ungetch('\x00') + curses.ungetch("\x00") caller.statusbar.check() caller.check() @@ -1833,7 +1885,7 @@ def newwin(background, *args): """Wrapper for curses.newwin to automatically set background colour on any newly created window.""" win = curses.newwin(*args) - win.bkgd(' ', background) + win.bkgd(" ", background) return win @@ -1860,8 +1912,7 @@ def curses_wrapper(func, *args, **kwargs): curses.endwin() -def main_curses(scr, args, config, interactive=True, locals_=None, - banner=None): +def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): """main function for the curses convenience wrapper Initialise the two main objects: the interpreter @@ -1879,12 +1930,14 @@ def main_curses(scr, args, config, interactive=True, locals_=None, global colors DO_RESIZE = False - if platform.system() != 'Windows': - old_sigwinch_handler = signal.signal(signal.SIGWINCH, - lambda *_: sigwinch(scr)) + if platform.system() != "Windows": + old_sigwinch_handler = signal.signal( + signal.SIGWINCH, lambda *_: sigwinch(scr) + ) # redraw window after being suspended old_sigcont_handler = signal.signal( - signal.SIGCONT, lambda *_: sigcont(scr)) + signal.SIGCONT, lambda *_: sigcont(scr) + ) stdscr = scr try: @@ -1924,7 +1977,7 @@ def main_curses(scr, args, config, interactive=True, locals_=None, curses.raw(False) return (exit_value, clirepl.getstdout()) else: - sys.path.insert(0, '') + sys.path.insert(0, "") try: clirepl.startup() except OSError as e: @@ -1934,11 +1987,11 @@ def main_curses(scr, args, config, interactive=True, locals_=None, if banner is not None: clirepl.write(banner) - clirepl.write('\n') + clirepl.write("\n") exit_value = clirepl.repl() - if hasattr(sys, 'exitfunc'): + if hasattr(sys, "exitfunc"): sys.exitfunc() - delattr(sys, 'exitfunc') + delattr(sys, "exitfunc") main_win.erase() main_win.refresh() @@ -1947,7 +2000,7 @@ def main_curses(scr, args, config, interactive=True, locals_=None, curses.raw(False) # Restore signal handlers - if platform.system() != 'Windows': + if platform.system() != "Windows": signal.signal(signal.SIGWINCH, old_sigwinch_handler) signal.signal(signal.SIGCONT, old_sigcont_handler) @@ -1966,8 +2019,13 @@ def main(args=None, locals_=None, banner=None): try: (exit_value, output) = curses_wrapper( - main_curses, exec_args, config, options.interactive, locals_, - banner=banner) + main_curses, + exec_args, + config, + options.interactive, + locals_, + banner=banner, + ) finally: sys.stdin = orig_stdin sys.stderr = orig_stderr @@ -1976,12 +2034,12 @@ def main(args=None, locals_=None, banner=None): # Fake stdout data so everything's still visible after exiting if config.flush_output and not options.quiet: sys.stdout.write(output) - if hasattr(sys.stdout, 'flush'): + if hasattr(sys.stdout, "flush"): sys.stdout.flush() return repl.extract_exit_value(exit_value) -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) # vim: sw=4 ts=4 sts=4 ai et diff --git a/bpython/clipboard.py b/bpython/clipboard.py index 89a419f32..aee429b9e 100644 --- a/bpython/clipboard.py +++ b/bpython/clipboard.py @@ -38,8 +38,9 @@ class XClipboard(object): """Manage clipboard with xclip.""" def copy(self, content): - process = subprocess.Popen(['xclip', '-i', '-selection', 'clipboard'], - stdin=subprocess.PIPE) + process = subprocess.Popen( + ["xclip", "-i", "-selection", "clipboard"], stdin=subprocess.PIPE + ) process.communicate(content.encode(getpreferredencoding())) if process.returncode != 0: raise CopyFailed() @@ -49,15 +50,16 @@ class OSXClipboard(object): """Manage clipboard with pbcopy.""" def copy(self, content): - process = subprocess.Popen(['pbcopy', 'w'], stdin=subprocess.PIPE) + process = subprocess.Popen(["pbcopy", "w"], stdin=subprocess.PIPE) process.communicate(content.encode(getpreferredencoding())) if process.returncode != 0: raise CopyFailed() def command_exists(command): - process = subprocess.Popen(['which', command], stderr=subprocess.STDOUT, - stdout=subprocess.PIPE) + process = subprocess.Popen( + ["which", command], stderr=subprocess.STDOUT, stdout=subprocess.PIPE + ) process.communicate() return process.returncode == 0 @@ -66,12 +68,14 @@ def command_exists(command): def get_clipboard(): """Get best clipboard handling implementation for current system.""" - if platform.system() == 'Darwin': - if command_exists('pbcopy'): + if platform.system() == "Darwin": + if command_exists("pbcopy"): return OSXClipboard() - if (platform.system() in ('Linux', 'FreeBSD', 'OpenBSD') and - os.getenv('DISPLAY') is not None): - if command_exists('xclip'): + if ( + platform.system() in ("Linux", "FreeBSD", "OpenBSD") + and os.getenv("DISPLAY") is not None + ): + if command_exists("xclip"): return XClipboard() return None diff --git a/bpython/config.py b/bpython/config.py index d9a2147cd..456479b7a 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -32,18 +32,18 @@ def can_encode(c): def supports_box_chars(): """Check if the encoding supports Unicode box characters.""" - return all(map(can_encode, u'│─└┘┌┐')) + return all(map(can_encode, "│─└┘┌┐")) def get_config_home(): """Returns the base directory for bpython's configuration files.""" - xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config') - return os.path.join(xdg_config_home, 'bpython') + xdg_config_home = os.environ.get("XDG_CONFIG_HOME", "~/.config") + return os.path.join(xdg_config_home, "bpython") def default_config_path(): """Returns bpython's default configuration file path.""" - return os.path.join(get_config_home(), 'config') + return os.path.join(get_config_home(), "config") def fill_config_with_default_values(config, default_values): @@ -53,7 +53,7 @@ def fill_config_with_default_values(config, default_values): for (opt, val) in iteritems(default_values[section]): if not config.has_option(section, opt): - config.set(section, opt, '%s' % (val, )) + config.set(section, opt, "%s" % (val,)) def loadini(struct, configfile): @@ -63,103 +63,104 @@ def loadini(struct, configfile): config = ConfigParser() defaults = { - 'general': { - 'arg_spec': True, - 'auto_display_list': True, - 'autocomplete_mode': default_completion, - 'color_scheme': 'default', - 'complete_magic_methods': True, - 'dedent_after': 1, - 'default_autoreload': False, - 'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')), - 'flush_output': True, - 'highlight_show_source': True, - 'hist_duplicates': True, - 'hist_file': '~/.pythonhist', - 'hist_length': 1000, - 'paste_time': 0.02, - 'pastebin_confirm': True, - 'pastebin_expiry': '1week', - 'pastebin_helper': '', - 'pastebin_removal_url': 'https://bpaste.net/remove/$removal_id', - 'pastebin_show_url': 'https://bpaste.net/show/$paste_id', - 'pastebin_url': 'https://bpaste.net/json/new', - 'save_append_py': False, - 'single_undo_time': 1.0, - 'syntax': True, - 'tab_length': 4, - 'unicode_box': True + "general": { + "arg_spec": True, + "auto_display_list": True, + "autocomplete_mode": default_completion, + "color_scheme": "default", + "complete_magic_methods": True, + "dedent_after": 1, + "default_autoreload": False, + "editor": os.environ.get("VISUAL", os.environ.get("EDITOR", "vi")), + "flush_output": True, + "highlight_show_source": True, + "hist_duplicates": True, + "hist_file": "~/.pythonhist", + "hist_length": 1000, + "paste_time": 0.02, + "pastebin_confirm": True, + "pastebin_expiry": "1week", + "pastebin_helper": "", + "pastebin_removal_url": "https://bpaste.net/remove/$removal_id", + "pastebin_show_url": "https://bpaste.net/show/$paste_id", + "pastebin_url": "https://bpaste.net/json/new", + "save_append_py": False, + "single_undo_time": 1.0, + "syntax": True, + "tab_length": 4, + "unicode_box": True, }, - 'keyboard': { - 'backspace': 'C-h', - 'beginning_of_line': 'C-a', - 'clear_line': 'C-u', - 'clear_screen': 'C-l', - 'clear_word': 'C-w', - 'copy_clipboard': 'F10', - 'cut_to_buffer': 'C-k', - 'delete': 'C-d', - 'down_one_line': 'C-n', - 'edit_config': 'F3', - 'edit_current_block': 'C-x', - 'end_of_line': 'C-e', - 'exit': '', - 'external_editor': 'F7', - 'help': 'F1', - 'incremental_search': 'M-s', - 'last_output': 'F9', - 'left': 'C-b', - 'pastebin': 'F8', - 'reimport': 'F6', - 'reverse_incremental_search': 'M-r', - 'right': 'C-f', - 'save': 'C-s', - 'search': 'C-o', - 'show_source': 'F2', - 'suspend': 'C-z', - 'toggle_file_watch': 'F5', - 'transpose_chars': 'C-t', - 'undo': 'C-r', - 'up_one_line': 'C-p', - 'yank_from_buffer': 'C-y' + "keyboard": { + "backspace": "C-h", + "beginning_of_line": "C-a", + "clear_line": "C-u", + "clear_screen": "C-l", + "clear_word": "C-w", + "copy_clipboard": "F10", + "cut_to_buffer": "C-k", + "delete": "C-d", + "down_one_line": "C-n", + "edit_config": "F3", + "edit_current_block": "C-x", + "end_of_line": "C-e", + "exit": "", + "external_editor": "F7", + "help": "F1", + "incremental_search": "M-s", + "last_output": "F9", + "left": "C-b", + "pastebin": "F8", + "reimport": "F6", + "reverse_incremental_search": "M-r", + "right": "C-f", + "save": "C-s", + "search": "C-o", + "show_source": "F2", + "suspend": "C-z", + "toggle_file_watch": "F5", + "transpose_chars": "C-t", + "undo": "C-r", + "up_one_line": "C-p", + "yank_from_buffer": "C-y", }, - 'cli': { - 'suggestion_width': 0.8, - 'trim_prompts': False, - }, - 'curtsies': { - 'list_above': False, - 'right_arrow_completion': True, - }} + "cli": {"suggestion_width": 0.8, "trim_prompts": False,}, + "curtsies": {"list_above": False, "right_arrow_completion": True,}, + } - default_keys_to_commands = dict((value, key) for (key, value) - in iteritems(defaults['keyboard'])) + default_keys_to_commands = dict( + (value, key) for (key, value) in iteritems(defaults["keyboard"]) + ) fill_config_with_default_values(config, defaults) try: if not config.read(config_path): # No config file. If the user has it in the old place then complain - if os.path.isfile(os.path.expanduser('~/.bpython.ini')): - sys.stderr.write("Error: It seems that you have a config file at " - "~/.bpython.ini. Please move your config file to " - "%s\n" % default_config_path()) + if os.path.isfile(os.path.expanduser("~/.bpython.ini")): + sys.stderr.write( + "Error: It seems that you have a config file at " + "~/.bpython.ini. Please move your config file to " + "%s\n" % default_config_path() + ) sys.exit(1) except UnicodeDecodeError as e: - sys.stderr.write("Error: Unable to parse config file at '{}' due to an " - "encoding issue. Please make sure to fix the encoding " - "of the file or remove it and then try again.\n".format(config_path)) + sys.stderr.write( + "Error: Unable to parse config file at '{}' due to an " + "encoding issue. Please make sure to fix the encoding " + "of the file or remove it and then try again.\n".format(config_path) + ) sys.exit(1) def get_key_no_doublebind(command): - default_commands_to_keys = defaults['keyboard'] - requested_key = config.get('keyboard', command) + default_commands_to_keys = defaults["keyboard"] + requested_key = config.get("keyboard", command) try: default_command = default_keys_to_commands[requested_key] - if (default_commands_to_keys[default_command] == - config.get('keyboard', default_command)): - setattr(struct, '%s_key' % default_command, '') + if default_commands_to_keys[default_command] == config.get( + "keyboard", default_command + ): + setattr(struct, "%s_key" % default_command, "") except KeyError: pass @@ -167,113 +168,117 @@ def get_key_no_doublebind(command): struct.config_path = config_path - struct.dedent_after = config.getint('general', 'dedent_after') - struct.tab_length = config.getint('general', 'tab_length') - struct.auto_display_list = config.getboolean('general', - 'auto_display_list') - struct.syntax = config.getboolean('general', 'syntax') - struct.arg_spec = config.getboolean('general', 'arg_spec') - struct.paste_time = config.getfloat('general', 'paste_time') - struct.single_undo_time = config.getfloat('general', 'single_undo_time') - struct.highlight_show_source = config.getboolean('general', - 'highlight_show_source') - struct.hist_file = config.get('general', 'hist_file') - struct.editor = config.get('general', 'editor') - struct.hist_length = config.getint('general', 'hist_length') - struct.hist_duplicates = config.getboolean('general', 'hist_duplicates') - struct.flush_output = config.getboolean('general', 'flush_output') + struct.dedent_after = config.getint("general", "dedent_after") + struct.tab_length = config.getint("general", "tab_length") + struct.auto_display_list = config.getboolean("general", "auto_display_list") + struct.syntax = config.getboolean("general", "syntax") + struct.arg_spec = config.getboolean("general", "arg_spec") + struct.paste_time = config.getfloat("general", "paste_time") + struct.single_undo_time = config.getfloat("general", "single_undo_time") + struct.highlight_show_source = config.getboolean( + "general", "highlight_show_source" + ) + struct.hist_file = config.get("general", "hist_file") + struct.editor = config.get("general", "editor") + struct.hist_length = config.getint("general", "hist_length") + struct.hist_duplicates = config.getboolean("general", "hist_duplicates") + struct.flush_output = config.getboolean("general", "flush_output") struct.default_autoreload = config.getboolean( - 'general', 'default_autoreload') - - struct.pastebin_key = get_key_no_doublebind('pastebin') - struct.copy_clipboard_key = get_key_no_doublebind('copy_clipboard') - struct.save_key = get_key_no_doublebind('save') - struct.search_key = get_key_no_doublebind('search') - struct.show_source_key = get_key_no_doublebind('show_source') - struct.suspend_key = get_key_no_doublebind('suspend') - struct.toggle_file_watch_key = get_key_no_doublebind('toggle_file_watch') - struct.undo_key = get_key_no_doublebind('undo') - struct.reimport_key = get_key_no_doublebind('reimport') + "general", "default_autoreload" + ) + + struct.pastebin_key = get_key_no_doublebind("pastebin") + struct.copy_clipboard_key = get_key_no_doublebind("copy_clipboard") + struct.save_key = get_key_no_doublebind("save") + struct.search_key = get_key_no_doublebind("search") + struct.show_source_key = get_key_no_doublebind("show_source") + struct.suspend_key = get_key_no_doublebind("suspend") + struct.toggle_file_watch_key = get_key_no_doublebind("toggle_file_watch") + struct.undo_key = get_key_no_doublebind("undo") + struct.reimport_key = get_key_no_doublebind("reimport") struct.reverse_incremental_search_key = get_key_no_doublebind( - 'reverse_incremental_search') - struct.incremental_search_key = get_key_no_doublebind('incremental_search') - struct.up_one_line_key = get_key_no_doublebind('up_one_line') - struct.down_one_line_key = get_key_no_doublebind('down_one_line') - struct.cut_to_buffer_key = get_key_no_doublebind('cut_to_buffer') - struct.yank_from_buffer_key = get_key_no_doublebind('yank_from_buffer') - struct.clear_word_key = get_key_no_doublebind('clear_word') - struct.backspace_key = get_key_no_doublebind('backspace') - struct.clear_line_key = get_key_no_doublebind('clear_line') - struct.clear_screen_key = get_key_no_doublebind('clear_screen') - struct.delete_key = get_key_no_doublebind('delete') - - struct.left_key = get_key_no_doublebind('left') - struct.right_key = get_key_no_doublebind('right') - struct.end_of_line_key = get_key_no_doublebind('end_of_line') - struct.beginning_of_line_key = get_key_no_doublebind('beginning_of_line') - struct.transpose_chars_key = get_key_no_doublebind('transpose_chars') - struct.exit_key = get_key_no_doublebind('exit') - struct.last_output_key = get_key_no_doublebind('last_output') - struct.edit_config_key = get_key_no_doublebind('edit_config') - struct.edit_current_block_key = get_key_no_doublebind('edit_current_block') - struct.external_editor_key = get_key_no_doublebind('external_editor') - struct.help_key = get_key_no_doublebind('help') - - struct.pastebin_confirm = config.getboolean('general', 'pastebin_confirm') - struct.pastebin_url = config.get('general', 'pastebin_url') - struct.pastebin_show_url = config.get('general', 'pastebin_show_url') - struct.pastebin_removal_url = config.get('general', 'pastebin_removal_url') - struct.pastebin_expiry = config.get('general', 'pastebin_expiry') - struct.pastebin_helper = config.get('general', 'pastebin_helper') - - struct.cli_suggestion_width = config.getfloat('cli', - 'suggestion_width') - struct.cli_trim_prompts = config.getboolean('cli', - 'trim_prompts') - - struct.complete_magic_methods = config.getboolean('general', - 'complete_magic_methods') - struct.autocomplete_mode = config.get('general', 'autocomplete_mode') - struct.save_append_py = config.getboolean('general', 'save_append_py') - - struct.curtsies_list_above = config.getboolean('curtsies', 'list_above') - struct.curtsies_right_arrow_completion = \ - config.getboolean('curtsies', 'right_arrow_completion') - - color_scheme_name = config.get('general', 'color_scheme') + "reverse_incremental_search" + ) + struct.incremental_search_key = get_key_no_doublebind("incremental_search") + struct.up_one_line_key = get_key_no_doublebind("up_one_line") + struct.down_one_line_key = get_key_no_doublebind("down_one_line") + struct.cut_to_buffer_key = get_key_no_doublebind("cut_to_buffer") + struct.yank_from_buffer_key = get_key_no_doublebind("yank_from_buffer") + struct.clear_word_key = get_key_no_doublebind("clear_word") + struct.backspace_key = get_key_no_doublebind("backspace") + struct.clear_line_key = get_key_no_doublebind("clear_line") + struct.clear_screen_key = get_key_no_doublebind("clear_screen") + struct.delete_key = get_key_no_doublebind("delete") + + struct.left_key = get_key_no_doublebind("left") + struct.right_key = get_key_no_doublebind("right") + struct.end_of_line_key = get_key_no_doublebind("end_of_line") + struct.beginning_of_line_key = get_key_no_doublebind("beginning_of_line") + struct.transpose_chars_key = get_key_no_doublebind("transpose_chars") + struct.exit_key = get_key_no_doublebind("exit") + struct.last_output_key = get_key_no_doublebind("last_output") + struct.edit_config_key = get_key_no_doublebind("edit_config") + struct.edit_current_block_key = get_key_no_doublebind("edit_current_block") + struct.external_editor_key = get_key_no_doublebind("external_editor") + struct.help_key = get_key_no_doublebind("help") + + struct.pastebin_confirm = config.getboolean("general", "pastebin_confirm") + struct.pastebin_url = config.get("general", "pastebin_url") + struct.pastebin_show_url = config.get("general", "pastebin_show_url") + struct.pastebin_removal_url = config.get("general", "pastebin_removal_url") + struct.pastebin_expiry = config.get("general", "pastebin_expiry") + struct.pastebin_helper = config.get("general", "pastebin_helper") + + struct.cli_suggestion_width = config.getfloat("cli", "suggestion_width") + struct.cli_trim_prompts = config.getboolean("cli", "trim_prompts") + + struct.complete_magic_methods = config.getboolean( + "general", "complete_magic_methods" + ) + struct.autocomplete_mode = config.get("general", "autocomplete_mode") + struct.save_append_py = config.getboolean("general", "save_append_py") + + struct.curtsies_list_above = config.getboolean("curtsies", "list_above") + struct.curtsies_right_arrow_completion = config.getboolean( + "curtsies", "right_arrow_completion" + ) + + color_scheme_name = config.get("general", "color_scheme") default_colors = { - 'keyword': 'y', - 'name': 'c', - 'comment': 'b', - 'string': 'm', - 'error': 'r', - 'number': 'G', - 'operator': 'Y', - 'punctuation': 'y', - 'token': 'C', - 'background': 'd', - 'output': 'w', - 'main': 'c', - 'paren': 'R', - 'prompt': 'c', - 'prompt_more': 'g', - 'right_arrow_suggestion': 'K', + "keyword": "y", + "name": "c", + "comment": "b", + "string": "m", + "error": "r", + "number": "G", + "operator": "Y", + "punctuation": "y", + "token": "C", + "background": "d", + "output": "w", + "main": "c", + "paren": "R", + "prompt": "c", + "prompt_more": "g", + "right_arrow_suggestion": "K", } - if color_scheme_name == 'default': + if color_scheme_name == "default": struct.color_scheme = default_colors else: struct.color_scheme = dict() - theme_filename = color_scheme_name + '.theme' - path = os.path.expanduser(os.path.join(get_config_home(), - theme_filename)) + theme_filename = color_scheme_name + ".theme" + path = os.path.expanduser( + os.path.join(get_config_home(), theme_filename) + ) try: load_theme(struct, path, struct.color_scheme, default_colors) except EnvironmentError: - sys.stderr.write("Could not load theme '%s'.\n" % - (color_scheme_name, )) + sys.stderr.write( + "Could not load theme '%s'.\n" % (color_scheme_name,) + ) sys.exit(1) # expand path of history file @@ -284,35 +289,35 @@ def get_key_no_doublebind(command): struct.autocomplete_mode = default_completion # set box drawing characters - if config.getboolean('general', 'unicode_box') and supports_box_chars(): - struct.left_border = '│' - struct.right_border = '│' - struct.top_border = '─' - struct.bottom_border = '─' - struct.left_bottom_corner = '└' - struct.right_bottom_corner = '┘' - struct.left_top_corner = '┌' - struct.right_top_corner = '┐' + if config.getboolean("general", "unicode_box") and supports_box_chars(): + struct.left_border = "│" + struct.right_border = "│" + struct.top_border = "─" + struct.bottom_border = "─" + struct.left_bottom_corner = "└" + struct.right_bottom_corner = "┘" + struct.left_top_corner = "┌" + struct.right_top_corner = "┐" else: - struct.left_border = '|' - struct.right_border = '|' - struct.top_border = '-' - struct.bottom_border = '-' - struct.left_bottom_corner = '+' - struct.right_bottom_corner = '+' - struct.left_top_corner = '+' - struct.right_top_corner = '+' + struct.left_border = "|" + struct.right_border = "|" + struct.top_border = "-" + struct.bottom_border = "-" + struct.left_bottom_corner = "+" + struct.right_bottom_corner = "+" + struct.left_top_corner = "+" + struct.right_top_corner = "+" def load_theme(struct, path, colors, default_colors): theme = ConfigParser() - with open(path, 'r') as f: + with open(path, "r") as f: theme.readfp(f) - for k, v in chain(theme.items('syntax'), theme.items('interface')): - if theme.has_option('syntax', k): - colors[k] = theme.get('syntax', k) + for k, v in chain(theme.items("syntax"), theme.items("interface")): + if theme.has_option("syntax", k): + colors[k] = theme.get("syntax", k) else: - colors[k] = theme.get('interface', k) + colors[k] = theme.get("interface", k) # Check against default theme to see if all values are defined for k, v in iteritems(default_colors): diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 92e4c8452..05a3e91c7 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -34,26 +34,31 @@ class FullCurtsiesRepl(BaseRepl): def __init__(self, config, locals_, banner, interp=None): self.input_generator = curtsies.input.Input( - keynames='curtsies', - sigint_event=True, - paste_threshold=None) + keynames="curtsies", sigint_event=True, paste_threshold=None + ) self.window = curtsies.window.CursorAwareWindow( sys.stdout, sys.stdin, keep_last_line=True, hide_cursor=False, - extra_bytes_callback=self.input_generator.unget_bytes) + extra_bytes_callback=self.input_generator.unget_bytes, + ) self._request_refresh = self.input_generator.event_trigger( - bpythonevents.RefreshRequestEvent) + bpythonevents.RefreshRequestEvent + ) self._schedule_refresh = self.input_generator.scheduled_event_trigger( - bpythonevents.ScheduledRefreshRequestEvent) + bpythonevents.ScheduledRefreshRequestEvent + ) self._request_reload = self.input_generator.threadsafe_event_trigger( - bpythonevents.ReloadEvent) - self.interrupting_refresh = (self.input_generator - .threadsafe_event_trigger(lambda: None)) + bpythonevents.ReloadEvent + ) + self.interrupting_refresh = self.input_generator.threadsafe_event_trigger( + lambda: None + ) self.request_undo = self.input_generator.event_trigger( - bpythonevents.UndoEvent) + bpythonevents.UndoEvent + ) with self.input_generator: pass # temp hack to get .original_stty @@ -63,7 +68,8 @@ def __init__(self, config, locals_, banner, interp=None): config=config, banner=banner, interp=interp, - orig_tcattrs=self.input_generator.original_stty) + orig_tcattrs=self.input_generator.original_stty, + ) def get_term_hw(self): return self.window.get_term_hw() @@ -91,8 +97,8 @@ def process_event_and_paint(self, e): except (SystemExitFromCodeRunner, SystemExit) as err: array, cursor_pos = self.paint( about_to_exit=True, - user_quit=isinstance(err, - SystemExitFromCodeRunner)) + user_quit=isinstance(err, SystemExitFromCodeRunner), + ) scrolled = self.window.render_to_terminal(array, cursor_pos) self.scroll_offset += scrolled raise @@ -133,25 +139,39 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): """ translations.init() - config, options, exec_args = bpargs.parse(args, ( - 'curtsies options', None, [ - Option('--log', '-L', action='count', - help=_("log debug messages to bpython.log")), - Option('--paste', '-p', action='store_true', - help=_("start by pasting lines of a file into session")), - ])) + config, options, exec_args = bpargs.parse( + args, + ( + "curtsies options", + None, + [ + Option( + "--log", + "-L", + action="count", + help=_("log debug messages to bpython.log"), + ), + Option( + "--paste", + "-p", + action="store_true", + help=_("start by pasting lines of a file into session"), + ), + ], + ), + ) if options.log is None: options.log = 0 logging_levels = [logging.ERROR, logging.INFO, logging.DEBUG] level = logging_levels[min(len(logging_levels) - 1, options.log)] - logging.getLogger('curtsies').setLevel(level) - logging.getLogger('bpython').setLevel(level) + logging.getLogger("curtsies").setLevel(level) + logging.getLogger("bpython").setLevel(level) if options.log: - handler = logging.FileHandler(filename='bpython.log') - logging.getLogger('curtsies').addHandler(handler) - logging.getLogger('curtsies').propagate = False - logging.getLogger('bpython').addHandler(handler) - logging.getLogger('bpython').propagate = False + handler = logging.FileHandler(filename="bpython.log") + logging.getLogger("curtsies").addHandler(handler) + logging.getLogger("curtsies").propagate = False + logging.getLogger("bpython").addHandler(handler) + logging.getLogger("bpython").propagate = False interp = None paste = None @@ -175,7 +195,7 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): return extract_exit_value(exit_value) else: # expected for interactive sessions (vanilla python does it) - sys.path.insert(0, '') + sys.path.insert(0, "") if not options.quiet: print(bpargs.version_banner()) @@ -196,7 +216,7 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): def _combined_events(event_provider, paste_threshold): """Combines consecutive keypress events into paste events.""" - timeout = yield 'nonsense_event' # so send can be used immediately + timeout = yield "nonsense_event" # so send can be used immediately queue = collections.deque() while True: e = event_provider.send(timeout) @@ -228,5 +248,5 @@ def combined_events(event_provider, paste_threshold=3): return g -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 7d458e646..4f17cb41f 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -42,7 +42,6 @@ def __call__(self, text): class _Helper(bpython._internal._Helper): - def __init__(self, repl=None): self._repl = repl pydoc.pager = self.pager @@ -61,4 +60,5 @@ def __call__(self, *args, **kwargs): else: return super(_Helper, self).__call__(*args, **kwargs) + # vim: sw=4 ts=4 sts=4 ai et diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index f9cd19589..ceaab8838 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -86,6 +86,7 @@ class CodeRunner(object): just passes whatever is passed in to run_code(for_code) to the code greenlet """ + def __init__(self, interp=None, request_refresh=lambda: None): """ interp is an interpreter object to use. By default a new one is @@ -113,8 +114,9 @@ def running(self): def load_code(self, source): """Prep code to be run""" - assert self.source is None, "you shouldn't load code when some is " \ - "already running" + assert self.source is None, ( + "you shouldn't load code when some is " "already running" + ) self.source = source self.code_context = None @@ -149,10 +151,11 @@ def run_code(self, for_code=None): else: request = self.code_context.switch(for_code) - logger.debug('request received from code was %r', request) + logger.debug("request received from code was %r", request) if not isinstance(request, RequestFromCodeRunner): - raise ValueError("Not a valid value from code greenlet: %r" % - request) + raise ValueError( + "Not a valid value from code greenlet: %r" % request + ) if isinstance(request, (Wait, Refresh)): self.code_is_waiting = True if isinstance(request, Refresh): @@ -172,11 +175,13 @@ def sigint_handler(self, *args): """SIGINT handler to use while code is running or request being fulfilled""" if greenlet.getcurrent() is self.code_context: - logger.debug('sigint while running user code!') + logger.debug("sigint while running user code!") raise KeyboardInterrupt() else: - logger.debug('sigint while fulfilling code request sigint handler ' - 'running!') + logger.debug( + "sigint while fulfilling code request sigint handler " + "running!" + ) self.sigint_happened_in_main_context = True def _blocking_run_code(self): @@ -215,7 +220,7 @@ def __init__(self, coderunner, on_write, fileno=1): def write(self, s, *args, **kwargs): if not py3 and isinstance(s, str): - s = s.decode(getpreferredencoding(), 'ignore') + s = s.decode(getpreferredencoding(), "ignore") self.on_write(s, *args, **kwargs) return self.coderunner.request_from_main_context(force_refresh=True) diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index b7766fbe0..15065d36e 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -8,15 +8,17 @@ class ReloadEvent(curtsies.events.Event): """Request to rerun REPL session ASAP because imported modules changed""" - def __init__(self, files_modified=('?',)): + + def __init__(self, files_modified=("?",)): self.files_modified = files_modified def __repr__(self): - return "" % (' & '.join(self.files_modified)) + return "" % (" & ".join(self.files_modified)) class RefreshRequestEvent(curtsies.events.Event): """Request to refresh REPL display ASAP""" + def __repr__(self): return "" @@ -26,12 +28,14 @@ class ScheduledRefreshRequestEvent(curtsies.events.ScheduledEvent): Used to schedule the disappearance of status bar message that only shows for a few seconds""" + def __init__(self, when): super(ScheduledRefreshRequestEvent, self).__init__(when) def __repr__(self): - return ("" % - (self.when - time.time())) + return "" % ( + self.when - time.time() + ) class RunStartupFileEvent(curtsies.events.Event): @@ -40,5 +44,6 @@ class RunStartupFileEvent(curtsies.events.Event): class UndoEvent(curtsies.events.Event): """Request to undo.""" + def __init__(self, n=1): self.n = n diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 576852c03..37ea8f509 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -9,9 +9,13 @@ from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler except ImportError: + def ModuleChangedEventHandler(*args): return None + + else: + class ModuleChangedEventHandler(FileSystemEventHandler): def __init__(self, paths, on_change): self.dirs = defaultdict(set) @@ -35,7 +39,7 @@ def _add_module(self, path): path = os.path.abspath(path) for suff in importcompletion.SUFFIXES: if path.endswith(suff): - path = path[:-len(suff)] + path = path[: -len(suff)] break dirname = os.path.dirname(path) if dirname not in self.dirs: @@ -75,6 +79,6 @@ def deactivate(self): def on_any_event(self, event): dirpath = os.path.dirname(event.src_path) - paths = [path + '.py' for path in self.dirs[dirpath]] + paths = [path + ".py" for path in self.dirs[dirpath]] if event.src_path in paths: self.on_change(files_modified=[event.src_path]) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 80d1f45a1..65b554ead 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -26,18 +26,21 @@ class StatusBar(BpythonInteraction): functionality in a evented or callback style, but trying to integrate bpython.Repl code. """ - def __init__(self, - config, - permanent_text="", - request_refresh=lambda: None, - schedule_refresh=lambda when: None): - self._current_line = '' + + def __init__( + self, + config, + permanent_text="", + request_refresh=lambda: None, + schedule_refresh=lambda when: None, + ): + self._current_line = "" self.cursor_offset_in_line = 0 self.in_prompt = False self.in_confirm = False self.waiting_for_refresh = False - self.prompt = '' - self._message = '' + self.prompt = "" + self._message = "" self.message_start_time = time.time() self.message_time = 3 self.permanent_stack = [] @@ -51,7 +54,7 @@ def __init__(self, super(StatusBar, self).__init__(config) def push_permanent_message(self, msg): - self._message = '' + self._message = "" self.permanent_stack.append(msg) def pop_permanent_message(self, msg): @@ -72,9 +75,11 @@ def message(self, msg, schedule_refresh=True): self.schedule_refresh(time.time() + self.message_time) def _check_for_expired_message(self): - if (self._message and - time.time() > self.message_start_time + self.message_time): - self._message = '' + if ( + self._message + and time.time() > self.message_start_time + self.message_time + ): + self._message = "" def process_event(self, e): """Returns True if shutting down""" @@ -86,12 +91,13 @@ def process_event(self, e): for ee in e.events: # strip control seq self.add_normal_character(ee if len(ee) == 1 else ee[-1]) - elif e in [''] or isinstance(e, events.SigIntEvent): + elif e in [""] or isinstance(e, events.SigIntEvent): self.request_context.switch(False) self.escape() elif e in edit_keys: self.cursor_offset_in_line, self._current_line = edit_keys[e]( - self.cursor_offset_in_line, self._current_line) + self.cursor_offset_in_line, self._current_line + ) elif e == "": # TODO can this be removed? raise KeyboardInterrupt() elif e == "": # TODO this isn't a very intuitive behavior @@ -101,7 +107,7 @@ def process_event(self, e): self.escape() self.request_context.switch(line) elif self.in_confirm: - if e in ('y', 'Y'): + if e in ("y", "Y"): self.request_context.switch(True) else: self.request_context.switch(False) @@ -110,21 +116,23 @@ def process_event(self, e): self.add_normal_character(e) def add_normal_character(self, e): - if e == '': - e = ' ' + if e == "": + e = " " if len(e) > 1: return - self._current_line = (self._current_line[:self.cursor_offset_in_line] + - e + - self._current_line[self.cursor_offset_in_line:]) + self._current_line = ( + self._current_line[: self.cursor_offset_in_line] + + e + + self._current_line[self.cursor_offset_in_line :] + ) self.cursor_offset_in_line += 1 def escape(self): """unfocus from statusbar, clear prompt state, wait for notify call""" self.in_prompt = False self.in_confirm = False - self.prompt = '' - self._current_line = '' + self.prompt = "" + self._current_line = "" @property def current_line(self): @@ -137,7 +145,7 @@ def current_line(self): return self._message if self.permanent_stack: return self.permanent_stack[-1] - return '' + return "" @property def should_show_message(self): diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 139039f5b..57c694ce4 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -14,23 +14,23 @@ default_colors = { - Generic.Error: 'R', - Keyword: 'd', - Name: 'c', - Name.Builtin: 'g', - Comment: 'b', - String: 'm', - Error: 'r', - Literal: 'd', - Number: 'M', - Number.Integer: 'd', - Operator: 'd', - Punctuation: 'd', - Token: 'd', - Whitespace: 'd', - Token.Punctuation.Parenthesis: 'R', - Name.Function: 'd', - Name.Class: 'd' + Generic.Error: "R", + Keyword: "d", + Name: "c", + Name.Builtin: "g", + Comment: "b", + String: "m", + Error: "r", + Literal: "d", + Number: "M", + Number.Integer: "d", + Operator: "d", + Punctuation: "d", + Token: "d", + Whitespace: "d", + Token.Punctuation.Parenthesis: "R", + Name.Function: "d", + Name.Class: "d", } @@ -48,11 +48,11 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} for k, v in iteritems(color_scheme): - self.f_strings[k] = '\x01%s' % (v,) + self.f_strings[k] = "\x01%s" % (v,) super(BPythonFormatter, self).__init__(**options) def format(self, tokensource, outfile): - o = '' + o = "" for token, text in tokensource: while token not in self.f_strings: @@ -82,29 +82,30 @@ def write(err_line): self.outfile = self def writetb(self, lines): - tbtext = ''.join(lines) + tbtext = "".join(lines) lexer = get_lexer_by_name("pytb") self.format(tbtext, lexer) # TODO for tracebacks get_lexer_by_name("pytb", stripall=True) def format(self, tbtext, lexer): traceback_informative_formatter = BPythonFormatter(default_colors) - traceback_code_formatter = BPythonFormatter({Token: ('d')}) + traceback_code_formatter = BPythonFormatter({Token: ("d")}) tokens = list(lexer.get_tokens(tbtext)) no_format_mode = False cur_line = [] for token, text in tokens: - if text.endswith('\n'): + if text.endswith("\n"): cur_line.append((token, text)) if no_format_mode: traceback_code_formatter.format(cur_line, self.outfile) no_format_mode = False else: - traceback_informative_formatter.format(cur_line, - self.outfile) + traceback_informative_formatter.format( + cur_line, self.outfile + ) cur_line = [] - elif text == ' ' and cur_line == []: + elif text == " " and cur_line == []: no_format_mode = True cur_line.append((token, text)) else: diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 223ec9e71..7448d4bf8 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -24,9 +24,9 @@ class AbstractEdits(object): default_kwargs = { - 'line': 'hello world', - 'cursor_offset': 5, - 'cut_buffer': 'there', + "line": "hello world", + "cursor_offset": 5, + "cut_buffer": "there", } def __contains__(self, key): @@ -42,29 +42,36 @@ def add(self, key, func, overwrite=False): if overwrite: del self[key] else: - raise ValueError('key %r already has a mapping' % (key,)) + raise ValueError("key %r already has a mapping" % (key,)) params = getargspec(func) - args = dict((k, v) for k, v in iteritems(self.default_kwargs) - if k in params) + args = dict( + (k, v) for k, v in iteritems(self.default_kwargs) if k in params + ) r = func(**args) if len(r) == 2: - if hasattr(func, 'kills'): - raise ValueError('function %r returns two values, but has a ' - 'kills attribute' % (func,)) + if hasattr(func, "kills"): + raise ValueError( + "function %r returns two values, but has a " + "kills attribute" % (func,) + ) self.simple_edits[key] = func elif len(r) == 3: - if not hasattr(func, 'kills'): - raise ValueError('function %r returns three values, but has ' - 'no kills attribute' % (func,)) + if not hasattr(func, "kills"): + raise ValueError( + "function %r returns three values, but has " + "no kills attribute" % (func,) + ) self.cut_buffer_edits[key] = func else: - raise ValueError('return type of function %r not recognized' % - (func,)) + raise ValueError( + "return type of function %r not recognized" % (func,) + ) def add_config_attr(self, config_attr, func): if config_attr in self.awaiting_config: - raise ValueError('config attribute %r already has a mapping' % - (config_attr,)) + raise ValueError( + "config attribute %r already has a mapping" % (config_attr,) + ) self.awaiting_config[config_attr] = func def call(self, key, **kwargs): @@ -119,27 +126,42 @@ def __init__(self): def mapping_with_config(self, config, key_dispatch): """Creates a new mapping object by applying a config object""" - return ConfiguredEdits(self.simple_edits, self.cut_buffer_edits, - self.awaiting_config, config, key_dispatch) + return ConfiguredEdits( + self.simple_edits, + self.cut_buffer_edits, + self.awaiting_config, + config, + key_dispatch, + ) def on(self, key=None, config=None): if not ((key is None) ^ (config is None)): raise ValueError("Must use exactly one of key, config") if key is not None: + def add_to_keybinds(func): self.add(key, func) return func + return add_to_keybinds else: + def add_to_config(func): self.add_config_attr(config, func) return func + return add_to_config class ConfiguredEdits(AbstractEdits): - def __init__(self, simple_edits, cut_buffer_edits, awaiting_config, config, - key_dispatch): + def __init__( + self, + simple_edits, + cut_buffer_edits, + awaiting_config, + config, + key_dispatch, + ): self.simple_edits = dict(simple_edits) self.cut_buffer_edits = dict(cut_buffer_edits) for attr, func in awaiting_config.items(): @@ -160,35 +182,35 @@ def add(self, key, func): def kills_behind(func): - func.kills = 'behind' + func.kills = "behind" return func def kills_ahead(func): - func.kills = 'ahead' + func.kills = "ahead" return func -@edit_keys.on(config='left_key') -@edit_keys.on('') +@edit_keys.on(config="left_key") +@edit_keys.on("") def left_arrow(cursor_offset, line): return max(0, cursor_offset - 1), line -@edit_keys.on(config='right_key') -@edit_keys.on('') +@edit_keys.on(config="right_key") +@edit_keys.on("") def right_arrow(cursor_offset, line): return min(len(line), cursor_offset + 1), line -@edit_keys.on(config='beginning_of_line_key') -@edit_keys.on('') +@edit_keys.on(config="beginning_of_line_key") +@edit_keys.on("") def beginning_of_line(cursor_offset, line): return 0, line -@edit_keys.on(config='end_of_line_key') -@edit_keys.on('') +@edit_keys.on(config="end_of_line_key") +@edit_keys.on("") def end_of_line(cursor_offset, line): return len(line), line @@ -196,11 +218,11 @@ def end_of_line(cursor_offset, line): forward_word_re = LazyReCompile(r"\S\s") -@edit_keys.on('') -@edit_keys.on('') -@edit_keys.on('') +@edit_keys.on("") +@edit_keys.on("") +@edit_keys.on("") def forward_word(cursor_offset, line): - match = forward_word_re.search(line[cursor_offset:]+' ') + match = forward_word_re.search(line[cursor_offset:] + " ") delta = match.end() - 1 if match else 0 return (cursor_offset + delta, line) @@ -212,21 +234,20 @@ def last_word_pos(string): return index or 0 -@edit_keys.on('') -@edit_keys.on('') -@edit_keys.on('') +@edit_keys.on("") +@edit_keys.on("") +@edit_keys.on("") def back_word(cursor_offset, line): return (last_word_pos(line[:cursor_offset]), line) -@edit_keys.on('') +@edit_keys.on("") def delete(cursor_offset, line): - return (cursor_offset, - line[:cursor_offset] + line[cursor_offset+1:]) + return (cursor_offset, line[:cursor_offset] + line[cursor_offset + 1 :]) -@edit_keys.on('') -@edit_keys.on(config='backspace_key') +@edit_keys.on("") +@edit_keys.on(config="backspace_key") def backspace(cursor_offset, line): if cursor_offset == 0: return cursor_offset, line @@ -234,111 +255,127 @@ def backspace(cursor_offset, line): # front_white = len(line[:cursor_offset]) - \ # len(line[:cursor_offset].lstrip()) to_delete = ((cursor_offset - 1) % INDENT) + 1 - return (cursor_offset - to_delete, - line[:cursor_offset - to_delete] + line[cursor_offset:]) - return (cursor_offset - 1, - line[:cursor_offset - 1] + line[cursor_offset:]) + return ( + cursor_offset - to_delete, + line[: cursor_offset - to_delete] + line[cursor_offset:], + ) + return (cursor_offset - 1, line[: cursor_offset - 1] + line[cursor_offset:]) -@edit_keys.on(config='clear_line_key') +@edit_keys.on(config="clear_line_key") def delete_from_cursor_back(cursor_offset, line): return 0, line[cursor_offset:] -delete_rest_of_word_re = LazyReCompile(r'\w\b') +delete_rest_of_word_re = LazyReCompile(r"\w\b") -@edit_keys.on('') # option-d +@edit_keys.on("") # option-d @kills_ahead def delete_rest_of_word(cursor_offset, line): m = delete_rest_of_word_re.search(line[cursor_offset:]) if not m: - return cursor_offset, line, '' - return (cursor_offset, - line[:cursor_offset] + line[m.start()+cursor_offset+1:], - line[cursor_offset:m.start()+cursor_offset+1]) + return cursor_offset, line, "" + return ( + cursor_offset, + line[:cursor_offset] + line[m.start() + cursor_offset + 1 :], + line[cursor_offset : m.start() + cursor_offset + 1], + ) -delete_word_to_cursor_re = LazyReCompile(r'\s\S') +delete_word_to_cursor_re = LazyReCompile(r"\s\S") -@edit_keys.on(config='clear_word_key') +@edit_keys.on(config="clear_word_key") @kills_behind def delete_word_to_cursor(cursor_offset, line): start = 0 for match in delete_word_to_cursor_re.finditer(line[:cursor_offset]): start = match.start() + 1 - return (start, line[:start] + line[cursor_offset:], - line[start:cursor_offset]) + return ( + start, + line[:start] + line[cursor_offset:], + line[start:cursor_offset], + ) -@edit_keys.on('') +@edit_keys.on("") def yank_prev_prev_killed_text(cursor_offset, line, cut_buffer): # TODO not implemented - just prev - return (cursor_offset+len(cut_buffer), - line[:cursor_offset] + cut_buffer + line[cursor_offset:]) + return ( + cursor_offset + len(cut_buffer), + line[:cursor_offset] + cut_buffer + line[cursor_offset:], + ) -@edit_keys.on(config='yank_from_buffer_key') +@edit_keys.on(config="yank_from_buffer_key") def yank_prev_killed_text(cursor_offset, line, cut_buffer): - return (cursor_offset+len(cut_buffer), - line[:cursor_offset] + cut_buffer + line[cursor_offset:]) + return ( + cursor_offset + len(cut_buffer), + line[:cursor_offset] + cut_buffer + line[cursor_offset:], + ) -@edit_keys.on(config='transpose_chars_key') +@edit_keys.on(config="transpose_chars_key") def transpose_character_before_cursor(cursor_offset, line): if cursor_offset < 2: return cursor_offset, line if cursor_offset == len(line): return cursor_offset, line[:-2] + line[-1] + line[-2] - return (min(len(line), cursor_offset + 1), - line[:cursor_offset - 1] + - (line[cursor_offset] if len(line) > cursor_offset else '') + - line[cursor_offset - 1] + - line[cursor_offset + 1:]) + return ( + min(len(line), cursor_offset + 1), + line[: cursor_offset - 1] + + (line[cursor_offset] if len(line) > cursor_offset else "") + + line[cursor_offset - 1] + + line[cursor_offset + 1 :], + ) -@edit_keys.on('') +@edit_keys.on("") def transpose_word_before_cursor(cursor_offset, line): return cursor_offset, line # TODO Not implemented + # TODO undo all changes to line: meta-r # bonus functions (not part of readline) -@edit_keys.on('') +@edit_keys.on("") def uppercase_next_word(cursor_offset, line): return cursor_offset, line # TODO Not implemented -@edit_keys.on(config='cut_to_buffer_key') +@edit_keys.on(config="cut_to_buffer_key") @kills_ahead def delete_from_cursor_forward(cursor_offset, line): return cursor_offset, line[:cursor_offset], line[cursor_offset:] -@edit_keys.on('') +@edit_keys.on("") def titlecase_next_word(cursor_offset, line): return cursor_offset, line # TODO Not implemented -delete_word_from_cursor_back_re = LazyReCompile(r'^|\b\w') +delete_word_from_cursor_back_re = LazyReCompile(r"^|\b\w") -@edit_keys.on('') -@edit_keys.on('') +@edit_keys.on("") +@edit_keys.on("") @kills_behind def delete_word_from_cursor_back(cursor_offset, line): """Whatever my option-delete does in bash on my mac""" if not line: - return cursor_offset, line, '' + return cursor_offset, line, "" start = None for match in delete_word_from_cursor_back_re.finditer(line): if match.start() < cursor_offset: start = match.start() if start is not None: - return (start, line[:start] + line[cursor_offset:], - line[start:cursor_offset]) + return ( + start, + line[:start] + line[cursor_offset:], + line[start:cursor_offset], + ) else: - return cursor_offset, line, '' + return cursor_offset, line, "" diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 15a4c04d5..33f8c01ea 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -10,20 +10,20 @@ from curtsies.formatstring import fmtstr, FmtStr -cnames = dict(zip('krgybmcwd', colors + ('default',))) +cnames = dict(zip("krgybmcwd", colors + ("default",))) -def func_for_letter(l, default='k'): +def func_for_letter(l, default="k"): """Returns FmtStr constructor for a bpython-style color code""" - if l == 'd': + if l == "d": l = default - elif l == 'D': + elif l == "D": l = default.upper() return partial(fmtstr, fg=cnames[l.lower()], bold=l.isupper()) -def color_for_letter(l, default='k'): - if l == 'd': +def color_for_letter(l, default="k"): + if l == "d": l = default return cnames[l.lower()] @@ -37,35 +37,38 @@ def parse(s): break start, rest = peel_off_string(rest) stuff.append(start) - return (sum((fs_from_match(d) for d in stuff[1:]), fs_from_match(stuff[0])) - if len(stuff) > 0 - else FmtStr()) + return ( + sum((fs_from_match(d) for d in stuff[1:]), fs_from_match(stuff[0])) + if len(stuff) > 0 + else FmtStr() + ) def fs_from_match(d): atts = {} - if d['fg']: + if d["fg"]: # this isn't according to spec as I understand it - if d['fg'].isupper(): - d['bold'] = True + if d["fg"].isupper(): + d["bold"] = True # TODO figure out why boldness isn't based on presence of \x02 - color = cnames[d['fg'].lower()] - if color != 'default': - atts['fg'] = FG_COLORS[color] - if d['bg']: - if d['bg'] == 'I': + color = cnames[d["fg"].lower()] + if color != "default": + atts["fg"] = FG_COLORS[color] + if d["bg"]: + if d["bg"] == "I": # hack for finding the "inverse" - color = colors[(colors.index(color) + (len(colors) // 2)) % - len(colors)] + color = colors[ + (colors.index(color) + (len(colors) // 2)) % len(colors) + ] else: - color = cnames[d['bg'].lower()] - if color != 'default': - atts['bg'] = BG_COLORS[color] - if d['bold']: - atts['bold'] = True - return fmtstr(d['string'], **atts) + color = cnames[d["bg"].lower()] + if color != "default": + atts["bg"] = BG_COLORS[color] + if d["bold"]: + atts["bold"] = True + return fmtstr(d["string"], **atts) peel_off_string_re = LazyReCompile( @@ -77,13 +80,15 @@ def fs_from_match(d): (?P[^\x04]*) \x04 (?P.*) - """, re.VERBOSE | re.DOTALL) + """, + re.VERBOSE | re.DOTALL, +) def peel_off_string(s): m = peel_off_string_re.match(s) assert m, repr(s) d = m.groupdict() - rest = d['rest'] - del d['rest'] + rest = d["rest"] + del d["rest"] return d, rest diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index 279d25d9e..8169ee46a 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -8,23 +8,22 @@ # TODO specifically catch IndentationErrors instead of any syntax errors -indent_empty_lines_re = LazyReCompile(r'\s*') -tabs_to_spaces_re = LazyReCompile(r'^\t+') +indent_empty_lines_re = LazyReCompile(r"\s*") +tabs_to_spaces_re = LazyReCompile(r"^\t+") def indent_empty_lines(s, compiler): """Indents blank lines that would otherwise cause early compilation Only really works if starting on a new line""" - lines = s.split('\n') + lines = s.split("\n") ends_with_newline = False if lines and not lines[-1]: ends_with_newline = True lines.pop() result_lines = [] - for p_line, line, n_line in zip([''] + lines[:-1], lines, - lines[1:] + ['']): + for p_line, line, n_line in zip([""] + lines[:-1], lines, lines[1:] + [""]): if len(line) == 0: p_indent = indent_empty_lines_re.match(p_line).group() n_indent = indent_empty_lines_re.match(n_line).group() @@ -32,18 +31,19 @@ def indent_empty_lines(s, compiler): else: result_lines.append(line) - return '\n'.join(result_lines) + ('\n' if ends_with_newline else '') + return "\n".join(result_lines) + ("\n" if ends_with_newline else "") def leading_tabs_to_spaces(s): - lines = s.split('\n') + lines = s.split("\n") result_lines = [] def tab_to_space(m): - return len(m.group()) * 4 * ' ' + return len(m.group()) * 4 * " " + for line in lines: result_lines.append(tabs_to_spaces_re.sub(tab_to_space, line)) - return '\n'.join(result_lines) + return "\n".join(result_lines) def preprocess(s, compiler): diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 6b415fabd..d4b18e387 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -29,8 +29,12 @@ import bpython from bpython.repl import Repl as BpythonRepl, SourceNotFound -from bpython.config import (Struct, loadini, default_config_path, - getpreferredencoding) +from bpython.config import ( + Struct, + loadini, + default_config_path, + getpreferredencoding, +) from bpython.formatter import BPythonFormatter from bpython import autocomplete from bpython.translations import _ @@ -47,8 +51,10 @@ from bpython.curtsiesfrontend.parse import parse as bpythonparse from bpython.curtsiesfrontend.parse import func_for_letter, color_for_letter from bpython.curtsiesfrontend.preprocess import preprocess -from bpython.curtsiesfrontend.interpreter import (Interp, - code_finished_will_parse) +from bpython.curtsiesfrontend.interpreter import ( + Interp, + code_finished_will_parse, +) from curtsies.configfile_keynames import keymap as key_dispatch @@ -83,7 +89,7 @@ See {example_config_url} for an example config file. Press {config.edit_config_key} to edit this config file. """ -EXAMPLE_CONFIG_URL = 'https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config' +EXAMPLE_CONFIG_URL = "https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config" EDIT_SESSION_HEADER = """### current bpython session - make changes and save to reevaluate session. ### lines beginning with ### will be ignored. ### To return to bpython without reevaluating make no changes to this file @@ -104,11 +110,12 @@ class FakeStdin(object): In user code, sys.stdin.read() asks the user for interactive input, so this class returns control to the UI to get that input.""" + def __init__(self, coderunner, repl, configured_edit_keys=None): self.coderunner = coderunner self.repl = repl self.has_focus = False # whether FakeStdin receives keypress events - self.current_line = '' + self.current_line = "" self.cursor_offset = 0 self.old_num_lines = 0 self.readline_results = [] @@ -120,18 +127,19 @@ def __init__(self, coderunner, repl, configured_edit_keys=None): def process_event(self, e): assert self.has_focus - logger.debug('fake input processing event %r', e) + logger.debug("fake input processing event %r", e) if isinstance(e, events.PasteEvent): for ee in e.events: if ee not in self.rl_char_sequences: self.add_input_character(ee) elif e in self.rl_char_sequences: self.cursor_offset, self.current_line = self.rl_char_sequences[e]( - self.cursor_offset, self.current_line) + self.cursor_offset, self.current_line + ) elif isinstance(e, events.SigIntEvent): self.coderunner.sigint_happened_in_main_context = True self.has_focus = False - self.current_line = '' + self.current_line = "" self.cursor_offset = 0 self.repl.run_code_and_maybe_finish() elif e in ("",): @@ -139,22 +147,22 @@ def process_event(self, e): elif e in [""]: pass - elif e in ['']: - if self.current_line == '': - self.repl.send_to_stdin('\n') + elif e in [""]: + if self.current_line == "": + self.repl.send_to_stdin("\n") self.has_focus = False - self.current_line = '' + self.current_line = "" self.cursor_offset = 0 - self.repl.run_code_and_maybe_finish(for_code='') + self.repl.run_code_and_maybe_finish(for_code="") else: pass elif e in ["\n", "\r", "", ""]: line = self.current_line - self.repl.send_to_stdin(line + '\n') + self.repl.send_to_stdin(line + "\n") self.has_focus = False - self.current_line = '' + self.current_line = "" self.cursor_offset = 0 - self.repl.run_code_and_maybe_finish(for_code=line + '\n') + self.repl.run_code_and_maybe_finish(for_code=line + "\n") else: # add normal character self.add_input_character(e) @@ -164,17 +172,19 @@ def process_event(self, e): self.repl.send_to_stdin(self.current_line) def add_input_character(self, e): - if e == '': - e = ' ' - if e.startswith('<') and e.endswith('>'): + if e == "": + e = " " + if e.startswith("<") and e.endswith(">"): return - assert len(e) == 1, 'added multiple characters: %r' % e - logger.debug('adding normal char %r to current line', e) - - c = e if py3 else e.encode('utf8') - self.current_line = (self.current_line[:self.cursor_offset] + - c + - self.current_line[self.cursor_offset:]) + assert len(e) == 1, "added multiple characters: %r" % e + logger.debug("adding normal char %r to current line", e) + + c = e if py3 else e.encode("utf8") + self.current_line = ( + self.current_line[: self.cursor_offset] + + c + + self.current_line[self.cursor_offset :] + ) self.cursor_offset += 1 def readline(self): @@ -185,7 +195,7 @@ def readline(self): return value def readlines(self, size=-1): - return list(iter(self.readline, '')) + return list(iter(self.readline, "")) def __iter__(self): return iter(self.readlines()) @@ -211,7 +221,7 @@ def close(self): @property def encoding(self): - return 'UTF8' + return "UTF8" # TODO write a read() method? @@ -219,6 +229,7 @@ def encoding(self): class ReevaluateFakeStdin(object): """Stdin mock used during reevaluation (undo) so raw_inputs don't have to be reentered""" + def __init__(self, fakestdin, repl): self.fakestdin = fakestdin self.repl = repl @@ -228,20 +239,19 @@ def readline(self): if self.readline_results: value = self.readline_results.pop(0) else: - value = 'no saved input available' + value = "no saved input available" self.repl.send_to_stdouterr(value) return value class ImportLoader(object): - def __init__(self, watcher, loader): self.watcher = watcher self.loader = loader def load_module(self, name): module = self.loader.load_module(name) - if hasattr(module, '__file__'): + if hasattr(module, "__file__"): self.watcher.track_module(module.__file__) return module @@ -249,20 +259,18 @@ def load_module(self, name): if not py3: # Remember that pkgutil.ImpLoader is an old style class. class ImpImportLoader(pkgutil.ImpLoader): - def __init__(self, watcher, *args): self.watcher = watcher pkgutil.ImpLoader.__init__(self, *args) def load_module(self, name): module = pkgutil.ImpLoader.load_module(self, name) - if hasattr(module, '__file__'): + if hasattr(module, "__file__"): self.watcher.track_module(module.__file__) return module class ImportFinder(object): - def __init__(self, watcher, old_meta_path): self.watcher = watcher self.old_meta_path = old_meta_path @@ -307,12 +315,14 @@ class BaseRepl(BpythonRepl): Subclasses are responsible for implementing several methods. """ - def __init__(self, - locals_=None, - config=None, - banner=None, - interp=None, - orig_tcattrs=None): + def __init__( + self, + locals_=None, + config=None, + banner=None, + interp=None, + orig_tcattrs=None, + ): """ locals_ is a mapping of locals to pass into the interpreter config is a bpython config.Struct with config attributes @@ -336,8 +346,11 @@ def __init__(self, interp.write = self.send_to_stdouterr if banner is None: if config.help_key: - banner = (_('Welcome to bpython!') + ' ' + - _('Press <%s> for help.') % config.help_key) + banner = ( + _("Welcome to bpython!") + + " " + + _("Press <%s> for help.") % config.help_key + ) else: banner = None # only one implemented currently @@ -348,9 +361,12 @@ def __init__(self, self.reevaluating = False self.fake_refresh_requested = False - self.status_bar = StatusBar(config, '', - request_refresh=self.request_refresh, - schedule_refresh=self.schedule_refresh) + self.status_bar = StatusBar( + config, + "", + request_refresh=self.request_refresh, + schedule_refresh=self.schedule_refresh, + ) self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch) logger.debug("starting parent init") super(BaseRepl, self).__init__(interp, config) @@ -363,10 +379,10 @@ def __init__(self, self.interact = self.status_bar # line currently being edited, without ps1 (usually '>>> ') - self._current_line = '' + self._current_line = "" # current line of output - stdout and stdin go here - self.current_stdouterr_line = '' + self.current_stdouterr_line = "" # lines separated whenever logical line # length goes over what the terminal width @@ -393,10 +409,16 @@ def __init__(self, # filenos match the backing device for libs that expect it, # but writing to them will do weird things to the display - self.stdout = FakeOutput(self.coderunner, self.send_to_stdouterr, - fileno=sys.__stdout__.fileno()) - self.stderr = FakeOutput(self.coderunner, self.send_to_stdouterr, - fileno=sys.__stderr__.fileno()) + self.stdout = FakeOutput( + self.coderunner, + self.send_to_stdouterr, + fileno=sys.__stdout__.fileno(), + ) + self.stderr = FakeOutput( + self.coderunner, + self.send_to_stdouterr, + fileno=sys.__stderr__.fileno(), + ) self.stdin = FakeStdin(self.coderunner, self, self.edit_keys) # next paint should clear screen @@ -427,7 +449,7 @@ def __init__(self, # 'reverse_incremental_search', 'incremental_search' or None self.incr_search_mode = None - self.incr_search_target = '' + self.incr_search_target = "" self.original_modules = set(sys.modules.keys()) @@ -455,7 +477,7 @@ def get_term_hw(self): """Returns the current width and height of the display area.""" return (50, 10) - def _schedule_refresh(self, when='now'): + def _schedule_refresh(self, when="now"): """Arrange for the bpython display to be refreshed soon. This method will be called when the Repl wants the display to be @@ -479,7 +501,7 @@ def _request_refresh(self): RefreshRequestEvent.""" raise NotImplementedError - def _request_reload(self, files_modified=('?',)): + def _request_reload(self, files_modified=("?",)): """Like request_refresh, but for reload requests events.""" raise NotImplementedError @@ -514,7 +536,7 @@ def request_reload(self, files_modified=()): if self.watching_files: self._request_reload(files_modified=files_modified) - def schedule_refresh(self, when='now'): + def schedule_refresh(self, when="now"): """Schedule a ScheduledRefreshRequestEvent for when. Such a event should interrupt if blockied waiting for keyboard input""" @@ -562,10 +584,16 @@ def sigwinch_handler(self, signum, frame): self.height, self.width = self.get_term_hw() cursor_dy = self.get_cursor_vertical_diff() self.scroll_offset -= cursor_dy - logger.info('sigwinch! Changed from %r to %r', (old_rows, old_columns), - (self.height, self.width)) - logger.info('decreasing scroll offset by %d to %d', cursor_dy, - self.scroll_offset) + logger.info( + "sigwinch! Changed from %r to %r", + (old_rows, old_columns), + (self.height, self.width), + ) + logger.info( + "decreasing scroll offset by %d to %d", + cursor_dy, + self.scroll_offset, + ) def sigtstp_handler(self, signum, frame): self.scroll_offset = len(self.lines_for_display) @@ -577,7 +605,7 @@ def sigtstp_handler(self, signum, frame): def clean_up_current_line_for_exit(self): """Called when trying to exit to prep for final paint""" - logger.debug('unhighlighting paren for exit') + logger.debug("unhighlighting paren for exit") self.cursor_offset = -1 self.unhighlight_paren() @@ -601,7 +629,7 @@ def process_control_event(self, e): pass elif isinstance(e, bpythonevents.RefreshRequestEvent): - logger.info('received ASAP refresh request event') + logger.info("received ASAP refresh request event") if self.status_bar.has_focus: self.status_bar.process_event(e) else: @@ -618,8 +646,9 @@ def process_control_event(self, e): return self.process_event(ctrl_char) with self.in_paste_mode(): # Might not really be a paste, UI might just be lagging - if (len(e.events) <= MAX_EVENTS_POSSIBLY_NOT_PASTE and - any(not is_simple_event(ee) for ee in e.events)): + if len(e.events) <= MAX_EVENTS_POSSIBLY_NOT_PASTE and any( + not is_simple_event(ee) for ee in e.events + ): for ee in e.events: if self.stdin.has_focus: self.stdin.process_event(ee) @@ -627,8 +656,9 @@ def process_control_event(self, e): self.process_event(ee) else: simple_events = just_simple_events(e.events) - source = preprocess(''.join(simple_events), - self.interp.compile) + source = preprocess( + "".join(simple_events), self.interp.compile + ) for ee in source: if self.stdin.has_focus: self.stdin.process_event(ee) @@ -640,7 +670,8 @@ def process_control_event(self, e): self.startup() except IOError as e: self.status_bar.message( - _('Executing PYTHONSTARTUP failed: %s') % (e, )) + _("Executing PYTHONSTARTUP failed: %s") % (e,) + ) elif isinstance(e, bpythonevents.UndoEvent): self.undo(n=e.n) @@ -649,7 +680,7 @@ def process_control_event(self, e): return self.stdin.process_event(e) elif isinstance(e, events.SigIntEvent): - logger.debug('received sigint event') + logger.debug("received sigint event") self.keyboard_interrupt() return @@ -657,9 +688,9 @@ def process_control_event(self, e): if self.watching_files: self.clear_modules_and_reevaluate() self.status_bar.message( - _('Reloaded at %s because %s modified.') % ( - time.strftime('%X'), - ' & '.join(e.files_modified))) + _("Reloaded at %s because %s modified.") + % (time.strftime("%X"), " & ".join(e.files_modified)) + ) else: raise ValueError("Don't know how to handle event type: %r" % e) @@ -672,11 +703,16 @@ def process_key_event(self, e): if self.stdin.has_focus: return self.stdin.process_event(e) - if (e in (key_dispatch[self.config.right_key] + - key_dispatch[self.config.end_of_line_key] + - ("",)) and - self.config.curtsies_right_arrow_completion and - self.cursor_offset == len(self.current_line)): + if ( + e + in ( + key_dispatch[self.config.right_key] + + key_dispatch[self.config.end_of_line_key] + + ("",) + ) + and self.config.curtsies_right_arrow_completion + and self.cursor_offset == len(self.current_line) + ): self.current_line += self.current_suggestion self.cursor_offset = len(self.current_line) @@ -694,9 +730,10 @@ def process_key_event(self, e): self.incremental_search(reverse=True) elif e in key_dispatch[self.config.incremental_search_key]: self.incremental_search() - elif (e in (("",) + - key_dispatch[self.config.backspace_key]) and - self.incr_search_mode): + elif ( + e in (("",) + key_dispatch[self.config.backspace_key]) + and self.incr_search_mode + ): self.add_to_incremental_search(self, backspace=True) elif e in self.edit_keys.cut_buffer_edits: self.readline_kill(e) @@ -705,7 +742,8 @@ def process_key_event(self, e): e, cursor_offset=self.cursor_offset, line=self.current_line, - cut_buffer=self.cut_buffer) + cut_buffer=self.cut_buffer, + ) elif e in key_dispatch[self.config.cut_to_buffer_key]: self.cut_to_buffer() elif e in key_dispatch[self.config.reimport_key]: @@ -722,7 +760,7 @@ def process_key_event(self, e): raise SystemExit() elif e in ("\n", "\r", "", "", ""): self.on_enter() - elif e == '': # tab + elif e == "": # tab self.on_tab() elif e in ("",): self.on_tab(back=True) @@ -744,7 +782,7 @@ def process_key_event(self, e): elif e in [""]: self.incr_search_mode = None elif e in [""]: - self.add_normal_character(' ') + self.add_normal_character(" ") else: self.add_normal_character(e) @@ -753,46 +791,58 @@ def get_last_word(self): previous_word = _last_word(self.rl_history.entry) word = _last_word(self.rl_history.back()) line = self.current_line - self._set_current_line(line[:len(line) - len(previous_word)] + word, - reset_rl_history=False) + self._set_current_line( + line[: len(line) - len(previous_word)] + word, + reset_rl_history=False, + ) self._set_cursor_offset( self.cursor_offset - len(previous_word) + len(word), - reset_rl_history=False) + reset_rl_history=False, + ) def incremental_search(self, reverse=False, include_current=False): if self.incr_search_mode is None: self.rl_history.enter(self.current_line) - self.incr_search_target = '' + self.incr_search_target = "" else: if self.incr_search_target: - line = (self.rl_history.back( - False, search=True, - target=self.incr_search_target, - include_current=include_current) - if reverse else - self.rl_history.forward( - False, search=True, + line = ( + self.rl_history.back( + False, + search=True, + target=self.incr_search_target, + include_current=include_current, + ) + if reverse + else self.rl_history.forward( + False, + search=True, target=self.incr_search_target, - include_current=include_current)) - self._set_current_line(line, - reset_rl_history=False, - clear_special_mode=False) - self._set_cursor_offset(len(self.current_line), - reset_rl_history=False, - clear_special_mode=False) + include_current=include_current, + ) + ) + self._set_current_line( + line, reset_rl_history=False, clear_special_mode=False + ) + self._set_cursor_offset( + len(self.current_line), + reset_rl_history=False, + clear_special_mode=False, + ) if reverse: - self.incr_search_mode = 'reverse_incremental_search' + self.incr_search_mode = "reverse_incremental_search" else: - self.incr_search_mode = 'incremental_search' + self.incr_search_mode = "incremental_search" def readline_kill(self, e): func = self.edit_keys[e] - self.cursor_offset, self.current_line, cut = func(self.cursor_offset, - self.current_line) + self.cursor_offset, self.current_line, cut = func( + self.cursor_offset, self.current_line + ) if self.last_events[-2] == e: # consecutive kill commands accumulative - if func.kills == 'ahead': + if func.kills == "ahead": self.cut_buffer += cut - elif func.kills == 'behind': + elif func.kills == "behind": self.cut_buffer = cut + self.cut_buffer else: raise ValueError("cut value other than 'ahead' or 'behind'") @@ -821,15 +871,16 @@ def on_tab(self, back=False): def only_whitespace_left_of_cursor(): """returns true if all characters before cursor are whitespace""" - return not self.current_line[:self.cursor_offset].strip() + return not self.current_line[: self.cursor_offset].strip() - logger.debug('self.matches_iter.matches:%r', self.matches_iter.matches) + logger.debug("self.matches_iter.matches:%r", self.matches_iter.matches) if only_whitespace_left_of_cursor(): - front_ws = (len(self.current_line[:self.cursor_offset]) - - len(self.current_line[:self.cursor_offset].lstrip())) + front_ws = len(self.current_line[: self.cursor_offset]) - len( + self.current_line[: self.cursor_offset].lstrip() + ) to_add = 4 - (front_ws % self.config.tab_length) for unused in range(to_add): - self.add_normal_character(' ') + self.add_normal_character(" ") return # run complete() if we don't already have matches @@ -845,23 +896,26 @@ def only_whitespace_left_of_cursor(): self.list_win_visible = self.complete() elif self.matches_iter.matches: - self.current_match = (back and self.matches_iter.previous() or - next(self.matches_iter)) + self.current_match = ( + back and self.matches_iter.previous() or next(self.matches_iter) + ) cursor_and_line = self.matches_iter.cur_line() self._cursor_offset, self._current_line = cursor_and_line # using _current_line so we don't trigger a completion reset self.list_win_visible = True def on_control_d(self): - if self.current_line == '': + if self.current_line == "": raise SystemExit() else: - self.current_line = (self.current_line[:self.cursor_offset] + - self.current_line[(self.cursor_offset + 1):]) + self.current_line = ( + self.current_line[: self.cursor_offset] + + self.current_line[(self.cursor_offset + 1) :] + ) def cut_to_buffer(self): - self.cut_buffer = self.current_line[self.cursor_offset:] - self.current_line = self.current_line[:self.cursor_offset] + self.cut_buffer = self.current_line[self.cursor_offset :] + self.current_line = self.current_line[: self.cursor_offset] def yank_from_buffer(self): pass @@ -873,20 +927,28 @@ def operate_and_get_next(self): def up_one_line(self): self.rl_history.enter(self.current_line) - self._set_current_line(tabs_to_spaces(self.rl_history.back( - False, - search=self.config.curtsies_right_arrow_completion)), + self._set_current_line( + tabs_to_spaces( + self.rl_history.back( + False, search=self.config.curtsies_right_arrow_completion + ) + ), update_completion=False, - reset_rl_history=False) + reset_rl_history=False, + ) self._set_cursor_offset(len(self.current_line), reset_rl_history=False) def down_one_line(self): self.rl_history.enter(self.current_line) - self._set_current_line(tabs_to_spaces(self.rl_history.forward( - False, - search=self.config.curtsies_right_arrow_completion)), + self._set_current_line( + tabs_to_spaces( + self.rl_history.forward( + False, search=self.config.curtsies_right_arrow_completion + ) + ), update_completion=False, - reset_rl_history=False) + reset_rl_history=False, + ) self._set_cursor_offset(len(self.current_line), reset_rl_history=False) def process_simple_keypress(self, e): @@ -898,17 +960,17 @@ def process_simple_keypress(self, e): self.process_event(bpythonevents.RefreshRequestEvent()) elif isinstance(e, events.Event): pass # ignore events - elif e == '': - self.add_normal_character(' ') + elif e == "": + self.add_normal_character(" ") else: self.add_normal_character(e) def send_current_block_to_external_editor(self, filename=None): text = self.send_to_external_editor(self.get_current_block()) - lines = [line for line in text.split('\n')] + lines = [line for line in text.split("\n")] while lines and not lines[-1].split(): lines.pop() - events = '\n'.join(lines + ([''] if len(lines) == 1 else ['', ''])) + events = "\n".join(lines + ([""] if len(lines) == 1 else ["", ""])) self.clear_current_block() with self.in_paste_mode(): for e in events: @@ -917,48 +979,53 @@ def send_current_block_to_external_editor(self, filename=None): def send_session_to_external_editor(self, filename=None): for_editor = EDIT_SESSION_HEADER - for_editor += '\n'.join(line[len(self.ps1):] - if line.startswith(self.ps1) else - line[len(self.ps2):] - if line.startswith(self.ps2) else - '### ' + line - for line in self.getstdout().split('\n')) + for_editor += "\n".join( + line[len(self.ps1) :] + if line.startswith(self.ps1) + else line[len(self.ps2) :] + if line.startswith(self.ps2) + else "### " + line + for line in self.getstdout().split("\n") + ) text = self.send_to_external_editor(for_editor) if text == for_editor: self.status_bar.message( - _('Session not reevaluated because it was not edited')) + _("Session not reevaluated because it was not edited") + ) return - lines = text.split('\n') + lines = text.split("\n") if not lines[-1].strip(): lines.pop() # strip last line if empty - if lines[-1].startswith('### '): + if lines[-1].startswith("### "): current_line = lines[-1][4:] else: - current_line = '' - from_editor = [line for line in lines if line[:3] != '###'] + current_line = "" + from_editor = [line for line in lines if line[:3] != "###"] if all(not line.strip() for line in from_editor): self.status_bar.message( - _('Session not reevaluated because saved file was blank')) + _("Session not reevaluated because saved file was blank") + ) return - source = preprocess('\n'.join(from_editor), self.interp.compile) - lines = source.split('\n') + source = preprocess("\n".join(from_editor), self.interp.compile) + lines = source.split("\n") self.history = lines self.reevaluate(insert_into_history=True) self.current_line = current_line self.cursor_offset = len(self.current_line) - self.status_bar.message(_('Session edited and reevaluated')) + self.status_bar.message(_("Session edited and reevaluated")) def clear_modules_and_reevaluate(self): if self.watcher: self.watcher.reset() cursor, line = self.cursor_offset, self.current_line - for modname in (set(sys.modules.keys()) - self.original_modules): + for modname in set(sys.modules.keys()) - self.original_modules: del sys.modules[modname] self.reevaluate(insert_into_history=True) self.cursor_offset, self.current_line = cursor, line - self.status_bar.message(_('Reloaded at %s by user.') % - (time.strftime('%X'), )) + self.status_bar.message( + _("Reloaded at %s by user.") % (time.strftime("%X"),) + ) def toggle_file_watch(self): if self.watcher: @@ -973,8 +1040,12 @@ def toggle_file_watch(self): self.watching_files = True self.watcher.activate() else: - self.status_bar.message(_('Auto-reloading not available because ' - 'watchdog not installed.')) + self.status_bar.message( + _( + "Auto-reloading not available because " + "watchdog not installed." + ) + ) # Handler Helpers def add_normal_character(self, char): @@ -983,15 +1054,20 @@ def add_normal_character(self, char): if self.incr_search_mode: self.add_to_incremental_search(char) else: - self._set_current_line((self.current_line[:self.cursor_offset] + - char + - self.current_line[self.cursor_offset:]), - update_completion=False, - reset_rl_history=False, - clear_special_mode=False) + self._set_current_line( + ( + self.current_line[: self.cursor_offset] + + char + + self.current_line[self.cursor_offset :] + ), + update_completion=False, + reset_rl_history=False, + clear_special_mode=False, + ) self.cursor_offset += 1 - if (self.config.cli_trim_prompts and - self.current_line.startswith(self.ps1)): + if self.config.cli_trim_prompts and self.current_line.startswith( + self.ps1 + ): self.current_line = self.current_line[4:] self.cursor_offset = max(0, self.cursor_offset - 4) @@ -1006,12 +1082,12 @@ def add_to_incremental_search(self, char=None, backspace=False): self.incr_search_target = self.incr_search_target[:-1] else: self.incr_search_target += char - if self.incr_search_mode == 'reverse_incremental_search': + if self.incr_search_mode == "reverse_incremental_search": self.incremental_search(reverse=True, include_current=True) - elif self.incr_search_mode == 'incremental_search': + elif self.incr_search_mode == "incremental_search": self.incremental_search(include_current=True) else: - raise ValueError('add_to_incremental_search not in a special mode') + raise ValueError("add_to_incremental_search not in a special mode") def update_completion(self, tab=False): """Update visible docstring and matches and box visibility""" @@ -1024,16 +1100,19 @@ def update_completion(self, tab=False): def predicted_indent(self, line): # TODO get rid of this! It's repeated code! Combine with Repl. - logger.debug('line is %r', line) - indent = len(re.match(r'[ ]*', line).group()) - if line.endswith(':'): + logger.debug("line is %r", line) + indent = len(re.match(r"[ ]*", line).group()) + if line.endswith(":"): indent = max(0, indent + self.config.tab_length) - elif line and line.count(' ') == len(line): + elif line and line.count(" ") == len(line): indent = max(0, indent - self.config.tab_length) - elif (line and ':' not in line and line.strip().startswith( - ('return', 'pass', 'raise', 'yield'))): + elif ( + line + and ":" not in line + and line.strip().startswith(("return", "pass", "raise", "yield")) + ): indent = max(0, indent - self.config.tab_length) - logger.debug('indent we found was %s', indent) + logger.debug("indent we found was %s", indent) return indent def push(self, line, insert_into_history=True): @@ -1047,12 +1126,16 @@ def push(self, line, insert_into_history=True): self.saved_indent = self.predicted_indent(line) if self.config.syntax: - display_line = bpythonparse(format( - self.tokenize(line), self.formatter)) + display_line = bpythonparse( + format(self.tokenize(line), self.formatter) + ) # self.tokenize requires that the line not be in self.buffer yet - logger.debug('display line being pushed to buffer: %r -> %r', - line, display_line) + logger.debug( + "display line being pushed to buffer: %r -> %r", + line, + display_line, + ) self.display_buffer.append(display_line) else: self.display_buffer.append(fmtstr(line)) @@ -1061,14 +1144,15 @@ def push(self, line, insert_into_history=True): self.insert_into_history(line) self.buffer.append(line) - code_to_run = '\n'.join(self.buffer) + code_to_run = "\n".join(self.buffer) - logger.debug('running %r in interpreter', self.buffer) - c, code_will_parse = code_finished_will_parse('\n'.join(self.buffer), - self.interp.compile) + logger.debug("running %r in interpreter", self.buffer) + c, code_will_parse = code_finished_will_parse( + "\n".join(self.buffer), self.interp.compile + ) self.saved_predicted_parse_error = not code_will_parse if c: - logger.debug('finished - buffer cleared') + logger.debug("finished - buffer cleared") self.cursor_offset = 0 self.display_lines.extend(self.display_buffer_lines) self.display_buffer = [] @@ -1090,10 +1174,12 @@ def run_code_and_maybe_finish(self, for_code=None): indent = 0 if self.rl_history.index == 0: - self._set_current_line(' ' * indent, update_completion=True) + self._set_current_line(" " * indent, update_completion=True) else: - self._set_current_line(self.rl_history.entries[-self.rl_history.index], - reset_rl_history=False) + self._set_current_line( + self.rl_history.entries[-self.rl_history.index], + reset_rl_history=False, + ) self.cursor_offset = len(self.current_line) def keyboard_interrupt(self): @@ -1101,10 +1187,12 @@ def keyboard_interrupt(self): self.cursor_offset = -1 self.unhighlight_paren() self.display_lines.extend(self.display_buffer_lines) - self.display_lines.extend(paint.display_linize( - self.current_cursor_line, self.width)) - self.display_lines.extend(paint.display_linize( - "KeyboardInterrupt", self.width)) + self.display_lines.extend( + paint.display_linize(self.current_cursor_line, self.width) + ) + self.display_lines.extend( + paint.display_linize("KeyboardInterrupt", self.width) + ) self.clear_current_block(remove_from_history=False) def unhighlight_paren(self): @@ -1119,12 +1207,14 @@ def unhighlight_paren(self): # then this is the current line, so don't worry about it return self.highlighted_paren = None - logger.debug('trying to unhighlight a paren on line %r', lineno) - logger.debug('with these tokens: %r', saved_tokens) + logger.debug("trying to unhighlight a paren on line %r", lineno) + logger.debug("with these tokens: %r", saved_tokens) new = bpythonparse(format(saved_tokens, self.formatter)) - self.display_buffer[lineno] = self.display_buffer[lineno] \ - .setslice_with_length(0, len(new), new, - len(self.display_buffer[lineno])) + self.display_buffer[lineno] = self.display_buffer[ + lineno + ].setslice_with_length( + 0, len(new), new, len(self.display_buffer[lineno]) + ) def clear_current_block(self, remove_from_history=True): self.display_buffer = [] @@ -1134,11 +1224,11 @@ def clear_current_block(self, remove_from_history=True): self.buffer = [] self.cursor_offset = 0 self.saved_indent = 0 - self.current_line = '' + self.current_line = "" self.cursor_offset = len(self.current_line) def get_current_block(self): - return '\n'.join(self.buffer + [self.current_line]) + return "\n".join(self.buffer + [self.current_line]) def send_to_stdouterr(self, output): """Send unicode strings or FmtStr to Repl stdout or stderr @@ -1147,24 +1237,33 @@ def send_to_stdouterr(self, output): tracebacks already formatted.""" if not output: return - lines = output.split('\n') - logger.debug('display_lines: %r', self.display_lines) + lines = output.split("\n") + logger.debug("display_lines: %r", self.display_lines) self.current_stdouterr_line += lines[0] if len(lines) > 1: - self.display_lines.extend(paint.display_linize( - self.current_stdouterr_line, self.width, blank_line=True)) self.display_lines.extend( - sum((paint.display_linize(line, self.width, - blank_line=True) - for line in lines[1:-1]), [])) + paint.display_linize( + self.current_stdouterr_line, self.width, blank_line=True + ) + ) + self.display_lines.extend( + sum( + ( + paint.display_linize(line, self.width, blank_line=True) + for line in lines[1:-1] + ), + [], + ) + ) self.current_stdouterr_line = lines[-1] - logger.debug('display_lines: %r', self.display_lines) + logger.debug("display_lines: %r", self.display_lines) def send_to_stdin(self, line): - if line.endswith('\n'): + if line.endswith("\n"): self.display_lines.extend( - paint.display_linize(self.current_output_line, self.width)) - self.current_output_line = '' + paint.display_linize(self.current_output_line, self.width) + ) + self.current_output_line = "" # formatting, output @property @@ -1177,22 +1276,29 @@ def done(self): def current_line_formatted(self): """The colored current line (no prompt, not wrapped)""" if self.config.syntax: - fs = bpythonparse(format(self.tokenize(self.current_line), - self.formatter)) + fs = bpythonparse( + format(self.tokenize(self.current_line), self.formatter) + ) if self.incr_search_mode: if self.incr_search_target in self.current_line: fs = fmtfuncs.on_magenta(self.incr_search_target).join( - fs.split(self.incr_search_target)) - elif (self.rl_history.saved_line and - self.rl_history.saved_line in self.current_line): - if (self.config.curtsies_right_arrow_completion and - self.rl_history.index != 0): + fs.split(self.incr_search_target) + ) + elif ( + self.rl_history.saved_line + and self.rl_history.saved_line in self.current_line + ): + if ( + self.config.curtsies_right_arrow_completion + and self.rl_history.index != 0 + ): fs = fmtfuncs.on_magenta(self.rl_history.saved_line).join( - fs.split(self.rl_history.saved_line)) - logger.debug('Display line %r -> %r', self.current_line, fs) + fs.split(self.rl_history.saved_line) + ) + logger.debug("Display line %r -> %r", self.current_line, fs) else: fs = fmtstr(self.current_line) - if hasattr(self, 'old_fs') and str(fs) != str(self.old_fs): + if hasattr(self, "old_fs") and str(fs) != str(self.old_fs): pass self.old_fs = fs return fs @@ -1207,10 +1313,11 @@ def display_buffer_lines(self): """The display lines (wrapped, colored, +prompts) of current buffer""" lines = [] for display_line in self.display_buffer: - prompt = func_for_letter(self.config.color_scheme['prompt']) - more = func_for_letter(self.config.color_scheme['prompt_more']) - display_line = ((more(self.ps2) if lines else prompt(self.ps1)) + - display_line) + prompt = func_for_letter(self.config.color_scheme["prompt"]) + more = func_for_letter(self.config.color_scheme["prompt_more"]) + display_line = ( + more(self.ps2) if lines else prompt(self.ps1) + ) + display_line for line in paint.display_linize(display_line, self.width): lines.append(line) return lines @@ -1218,33 +1325,42 @@ def display_buffer_lines(self): @property def display_line_with_prompt(self): """colored line with prompt""" - prompt = func_for_letter(self.config.color_scheme['prompt']) - more = func_for_letter(self.config.color_scheme['prompt_more']) - if self.incr_search_mode == 'reverse_incremental_search': - return (prompt('(reverse-i-search)`{}\': '.format( - self.incr_search_target)) + self.current_line_formatted) - elif self.incr_search_mode == 'incremental_search': - return (prompt('(i-search)`%s\': '.format( - self.incr_search_target)) + self.current_line_formatted) - return ((prompt(self.ps1) if self.done else more(self.ps2)) + - self.current_line_formatted) + prompt = func_for_letter(self.config.color_scheme["prompt"]) + more = func_for_letter(self.config.color_scheme["prompt_more"]) + if self.incr_search_mode == "reverse_incremental_search": + return ( + prompt( + "(reverse-i-search)`{}': ".format(self.incr_search_target) + ) + + self.current_line_formatted + ) + elif self.incr_search_mode == "incremental_search": + return ( + prompt("(i-search)`%s': ".format(self.incr_search_target)) + + self.current_line_formatted + ) + return ( + prompt(self.ps1) if self.done else more(self.ps2) + ) + self.current_line_formatted @property def current_cursor_line_without_suggestion(self): """Current line, either output/input or Python prompt + code""" - value = (self.current_output_line + - ('' if self.coderunner.running else - self.display_line_with_prompt)) - logger.debug('current cursor line: %r', value) + value = self.current_output_line + ( + "" if self.coderunner.running else self.display_line_with_prompt + ) + logger.debug("current cursor line: %r", value) return value @property def current_cursor_line(self): if self.config.curtsies_right_arrow_completion: suggest = func_for_letter( - self.config.color_scheme['right_arrow_suggestion']) - return (self.current_cursor_line_without_suggestion + - suggest(self.current_suggestion)) + self.config.color_scheme["right_arrow_suggestion"] + ) + return self.current_cursor_line_without_suggestion + suggest( + self.current_suggestion + ) else: return self.current_cursor_line_without_suggestion @@ -1253,8 +1369,8 @@ def current_suggestion(self): if self.current_line: for entry in reversed(self.rl_history.entries): if entry.startswith(self.current_line): - return entry[len(self.current_line):] - return '' + return entry[len(self.current_line) :] + return "" @property def current_output_line(self): @@ -1263,12 +1379,16 @@ def current_output_line(self): @current_output_line.setter def current_output_line(self, value): - self.current_stdouterr_line = '' - self.stdin.current_line = '\n' - - def paint(self, about_to_exit=False, user_quit=False, - try_preserve_history_height=30, - min_infobox_height=5): + self.current_stdouterr_line = "" + self.stdin.current_line = "\n" + + def paint( + self, + about_to_exit=False, + user_quit=False, + try_preserve_history_height=30, + min_infobox_height=5, + ): """Returns an array of min_height or more rows and width columns, plus cursor position @@ -1287,16 +1407,18 @@ def paint(self, about_to_exit=False, user_quit=False, self.clean_up_current_line_for_exit() width, min_height = self.width, self.height - show_status_bar = ((bool(self.status_bar.should_show_message) or - self.status_bar.has_focus) and - not self.request_paint_to_pad_bottom) + show_status_bar = ( + bool(self.status_bar.should_show_message) + or self.status_bar.has_focus + ) and not self.request_paint_to_pad_bottom if show_status_bar: # because we're going to tack the status bar on at the end, shoot # for an array one less than the height of the screen min_height -= 1 - current_line_start_row = (len(self.lines_for_display) - - max(0, self.scroll_offset)) + current_line_start_row = len(self.lines_for_display) - max( + 0, self.scroll_offset + ) # TODO how is the situation of self.scroll_offset < 0 possible? # or show_status_bar and about_to_exit ? if self.request_paint_to_clear_screen: @@ -1312,8 +1434,9 @@ def paint(self, about_to_exit=False, user_quit=False, # TODO test case of current line filling up the whole screen (there # aren't enough rows to show it) - current_line = paint.paint_current_line(min_height, width, - self.current_cursor_line) + current_line = paint.paint_current_line( + min_height, width, self.current_cursor_line + ) # needs to happen before we calculate contents of history because # calculating self.current_cursor_line has the side effect of # unhighlighting parens in buffer @@ -1321,22 +1444,28 @@ def paint(self, about_to_exit=False, user_quit=False, def move_screen_up(current_line_start_row): # move screen back up a screen minus a line while current_line_start_row < 0: - logger.debug('scroll_offset was %s, current_line_start_row ' - 'was %s', self.scroll_offset, - current_line_start_row) + logger.debug( + "scroll_offset was %s, current_line_start_row " "was %s", + self.scroll_offset, + current_line_start_row, + ) self.scroll_offset = self.scroll_offset - self.height - current_line_start_row = (len(self.lines_for_display) - - max(-1, self.scroll_offset)) - logger.debug('scroll_offset changed to %s, ' - 'current_line_start_row changed to %s', - self.scroll_offset, current_line_start_row) + current_line_start_row = len(self.lines_for_display) - max( + -1, self.scroll_offset + ) + logger.debug( + "scroll_offset changed to %s, " + "current_line_start_row changed to %s", + self.scroll_offset, + current_line_start_row, + ) return current_line_start_row if self.inconsistent_history and not self.history_already_messed_up: logger.debug(INCONSISTENT_HISTORY_MSG) self.history_already_messed_up = True msg = INCONSISTENT_HISTORY_MSG - arr[0, 0:min(len(msg), width)] = [msg[:width]] + arr[0, 0 : min(len(msg), width)] = [msg[:width]] current_line_start_row += 1 # for the message # to make up for the scroll that will be received after the @@ -1344,89 +1473,117 @@ def move_screen_up(current_line_start_row): self.scroll_offset -= 1 current_line_start_row = move_screen_up(current_line_start_row) - logger.debug('current_line_start_row: %r', current_line_start_row) + logger.debug("current_line_start_row: %r", current_line_start_row) - history = paint.paint_history(max(0, current_line_start_row - 1), - width, self.lines_for_display) - arr[1:history.height + 1, :history.width] = history + history = paint.paint_history( + max(0, current_line_start_row - 1), + width, + self.lines_for_display, + ) + arr[1 : history.height + 1, : history.width] = history if arr.height <= min_height: # force scroll down to hide broken history message - arr[min_height, 0] = ' ' + arr[min_height, 0] = " " elif current_line_start_row < 0: # if current line trying to be drawn off the top of the screen logger.debug(CONTIGUITY_BROKEN_MSG) msg = CONTIGUITY_BROKEN_MSG - arr[0, 0:min(len(msg), width)] = [msg[:width]] + arr[0, 0 : min(len(msg), width)] = [msg[:width]] current_line_start_row = move_screen_up(current_line_start_row) - history = paint.paint_history(max(0, current_line_start_row - 1), - width, self.lines_for_display) - arr[1:history.height + 1, :history.width] = history + history = paint.paint_history( + max(0, current_line_start_row - 1), + width, + self.lines_for_display, + ) + arr[1 : history.height + 1, : history.width] = history if arr.height <= min_height: # force scroll down to hide broken history message - arr[min_height, 0] = ' ' + arr[min_height, 0] = " " else: assert current_line_start_row >= 0 logger.debug("no history issues. start %i", current_line_start_row) - history = paint.paint_history(current_line_start_row, width, - self.lines_for_display) - arr[:history.height, :history.width] = history + history = paint.paint_history( + current_line_start_row, width, self.lines_for_display + ) + arr[: history.height, : history.width] = history self.inconsistent_history = False if user_quit: # quit() or exit() in interp - current_line_start_row = (current_line_start_row - - current_line.height) - logger.debug("---current line row slice %r, %r", - current_line_start_row, - current_line_start_row + current_line.height) + current_line_start_row = ( + current_line_start_row - current_line.height + ) + logger.debug( + "---current line row slice %r, %r", + current_line_start_row, + current_line_start_row + current_line.height, + ) logger.debug("---current line col slice %r, %r", 0, current_line.width) - arr[current_line_start_row:(current_line_start_row + - current_line.height), - 0:current_line.width] = current_line + arr[ + current_line_start_row : ( + current_line_start_row + current_line.height + ), + 0 : current_line.width, + ] = current_line if current_line.height > min_height: return arr, (0, 0) # short circuit, no room for infobox - lines = paint.display_linize(self.current_cursor_line + 'X', width) + lines = paint.display_linize(self.current_cursor_line + "X", width) # extra character for space for the cursor current_line_end_row = current_line_start_row + len(lines) - 1 current_line_height = current_line_end_row - current_line_start_row if self.stdin.has_focus: cursor_row, cursor_column = divmod( - len(self.current_stdouterr_line) + - self.stdin.cursor_offset, width) + len(self.current_stdouterr_line) + self.stdin.cursor_offset, + width, + ) assert cursor_column >= 0, cursor_column elif self.coderunner.running: # TODO does this ever happen? cursor_row, cursor_column = divmod( - (len(self.current_cursor_line_without_suggestion) + - self.cursor_offset), - width) + ( + len(self.current_cursor_line_without_suggestion) + + self.cursor_offset + ), + width, + ) assert cursor_column >= 0, ( - cursor_column, len(self.current_cursor_line), - len(self.current_line), self.cursor_offset) + cursor_column, + len(self.current_cursor_line), + len(self.current_line), + self.cursor_offset, + ) else: cursor_row, cursor_column = divmod( - (len(self.current_cursor_line_without_suggestion) - - len(self.current_line) + self.cursor_offset), - width) + ( + len(self.current_cursor_line_without_suggestion) + - len(self.current_line) + + self.cursor_offset + ), + width, + ) assert cursor_column >= 0, ( - cursor_column, len(self.current_cursor_line), - len(self.current_line), self.cursor_offset) + cursor_column, + len(self.current_cursor_line), + len(self.current_line), + self.cursor_offset, + ) cursor_row += current_line_start_row if self.list_win_visible and not self.coderunner.running: - logger.debug('infobox display code running') + logger.debug("infobox display code running") visible_space_above = history.height potential_space_below = min_height - current_line_end_row - 1 - visible_space_below = (potential_space_below - - self.get_top_usable_line()) + visible_space_below = ( + potential_space_below - self.get_top_usable_line() + ) if self.config.curtsies_list_above: info_max_rows = max(visible_space_above, visible_space_below) @@ -1434,65 +1591,84 @@ def move_screen_up(current_line_start_row): # Logic for determining size of completion box # smallest allowed over-full completion box preferred_height = max( - # always make infobox at least this height - min_infobox_height, - - # use this value if there's so much space that we can - # preserve this try_preserve_history_height rows history - min_height - try_preserve_history_height) - - info_max_rows = min(max(visible_space_below, - preferred_height), - min_height - current_line_height - 1) + # always make infobox at least this height + min_infobox_height, + # use this value if there's so much space that we can + # preserve this try_preserve_history_height rows history + min_height - try_preserve_history_height, + ) + + info_max_rows = min( + max(visible_space_below, preferred_height), + min_height - current_line_height - 1, + ) infobox = paint.paint_infobox( - info_max_rows, - int(width * self.config.cli_suggestion_width), - self.matches_iter.matches, - self.funcprops, - self.arg_pos, - self.current_match, - self.docstring, - self.config, - self.matches_iter.completer.format - if self.matches_iter.completer else None) - - if (visible_space_below >= infobox.height or - not self.config.curtsies_list_above): - arr[current_line_end_row + 1:(current_line_end_row + 1 + - infobox.height), - 0:infobox.width] = infobox + info_max_rows, + int(width * self.config.cli_suggestion_width), + self.matches_iter.matches, + self.funcprops, + self.arg_pos, + self.current_match, + self.docstring, + self.config, + self.matches_iter.completer.format + if self.matches_iter.completer + else None, + ) + + if ( + visible_space_below >= infobox.height + or not self.config.curtsies_list_above + ): + arr[ + current_line_end_row + + 1 : (current_line_end_row + 1 + infobox.height), + 0 : infobox.width, + ] = infobox else: - arr[current_line_start_row - infobox.height: - current_line_start_row, 0:infobox.width] = infobox - logger.debug('infobox of shape %r added to arr of shape %r', - infobox.shape, arr.shape) - - logger.debug('about to exit: %r', about_to_exit) + arr[ + current_line_start_row + - infobox.height : current_line_start_row, + 0 : infobox.width, + ] = infobox + logger.debug( + "infobox of shape %r added to arr of shape %r", + infobox.shape, + arr.shape, + ) + + logger.debug("about to exit: %r", about_to_exit) if show_status_bar: - statusbar_row = (min_height - if arr.height == min_height else arr.height) + statusbar_row = ( + min_height if arr.height == min_height else arr.height + ) if about_to_exit: arr[statusbar_row, :] = FSArray(1, width) else: arr[statusbar_row, :] = paint.paint_statusbar( - 1, width, self.status_bar.current_line, self.config) + 1, width, self.status_bar.current_line, self.config + ) if self.presentation_mode: rows = arr.height columns = arr.width last_key_box = paint.paint_last_events( - rows, columns, - [events.pp_event(x) for x in self.last_events if x], - self.config) - arr[arr.height - last_key_box.height:arr.height, - arr.width - last_key_box.width:arr.width] = last_key_box - - if self.config.color_scheme['background'] not in ('d', 'D'): + rows, + columns, + [events.pp_event(x) for x in self.last_events if x], + self.config, + ) + arr[ + arr.height - last_key_box.height : arr.height, + arr.width - last_key_box.width : arr.width, + ] = last_key_box + + if self.config.color_scheme["background"] not in ("d", "D"): for r in range(arr.height): - bg = color_for_letter(self.config.color_scheme['background']) + bg = color_for_letter(self.config.color_scheme["background"]) arr[r] = fmtstr(arr[r], bg=bg) - logger.debug('returning arr of size %r', arr.shape) - logger.debug('cursor pos: %r', (cursor_row, cursor_column)) + logger.debug("returning arr of size %r", arr.shape) + logger.debug("cursor pos: %r", (cursor_row, cursor_column)) return arr, (cursor_row, cursor_column) @contextlib.contextmanager @@ -1505,19 +1681,24 @@ def in_paste_mode(self): self.update_completion() def __repr__(self): - s = '' - s += '<' + repr(type(self)) + '\n' - s += " cursor_offset:" + repr(self.cursor_offset) + '\n' - s += " num display lines:" + repr(len(self.display_lines)) + '\n' - s += " lines scrolled down:" + repr(self.scroll_offset) + '\n' - s += '>' + s = "" + s += "<" + repr(type(self)) + "\n" + s += " cursor_offset:" + repr(self.cursor_offset) + "\n" + s += " num display lines:" + repr(len(self.display_lines)) + "\n" + s += " lines scrolled down:" + repr(self.scroll_offset) + "\n" + s += ">" return s def _get_current_line(self): return self._current_line - def _set_current_line(self, line, update_completion=True, - reset_rl_history=True, clear_special_mode=True): + def _set_current_line( + self, + line, + update_completion=True, + reset_rl_history=True, + clear_special_mode=True, + ): if self._current_line == line: return self._current_line = line @@ -1531,14 +1712,20 @@ def _set_current_line(self, line, update_completion=True, self.special_mode = None self.unhighlight_paren() - current_line = property(_get_current_line, _set_current_line, None, - "The current line") + current_line = property( + _get_current_line, _set_current_line, None, "The current line" + ) def _get_cursor_offset(self): return self._cursor_offset - def _set_cursor_offset(self, offset, update_completion=True, - reset_rl_history=False, clear_special_mode=True): + def _set_cursor_offset( + self, + offset, + update_completion=True, + reset_rl_history=False, + clear_special_mode=True, + ): if self._cursor_offset == offset: return if self.paste_mode: @@ -1554,9 +1741,12 @@ def _set_cursor_offset(self, offset, update_completion=True, self.update_completion() self.unhighlight_paren() - cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, - "The current cursor offset from the front of the " - "line") + cursor_offset = property( + _get_cursor_offset, + _set_cursor_offset, + None, + "The current cursor offset from the front of the " "line", + ) def echo(self, msg, redraw=True): """ @@ -1570,24 +1760,25 @@ def echo(self, msg, redraw=True): @property def cpos(self): - "many WATs were had - it's the pos from the end of the line back""" + "many WATs were had - it's the pos from the end of the line back" "" return len(self.current_line) - self.cursor_offset def reprint_line(self, lineno, tokens): logger.debug("calling reprint line with %r %r", lineno, tokens) if self.config.syntax: self.display_buffer[lineno] = bpythonparse( - format(tokens, self.formatter)) + format(tokens, self.formatter) + ) def take_back_buffer_line(self): assert len(self.buffer) > 0 if len(self.buffer) == 1: self._cursor_offset = 0 - self.current_line = '' + self.current_line = "" else: line = self.buffer[-1] indent = self.predicted_indent(line) - self._current_line = indent * ' ' + self._current_line = indent * " " self.cursor_offset = len(self.current_line) self.display_buffer.pop() self.buffer.pop() @@ -1642,40 +1833,53 @@ def reevaluate(self, insert_into_history=False): sys.stdin = self.stdin self.reevaluating = False - num_lines_onscreen = (len(self.lines_for_display) - - max(0, self.scroll_offset)) - display_lines_offscreen = self.display_lines[:len(self.display_lines) - - num_lines_onscreen] - old_display_lines_offscreen = old_display_lines[:( - len(self.display_lines) - num_lines_onscreen)] - logger.debug('old_display_lines_offscreen %s', '|'.join( - str(x) for x in old_display_lines_offscreen)) - logger.debug(' display_lines_offscreen %s', '|'.join( - str(x) for x in display_lines_offscreen)) - if ((old_display_lines_offscreen[:len(display_lines_offscreen)] != - display_lines_offscreen) and - not self.history_already_messed_up): + num_lines_onscreen = len(self.lines_for_display) - max( + 0, self.scroll_offset + ) + display_lines_offscreen = self.display_lines[ + : len(self.display_lines) - num_lines_onscreen + ] + old_display_lines_offscreen = old_display_lines[ + : (len(self.display_lines) - num_lines_onscreen) + ] + logger.debug( + "old_display_lines_offscreen %s", + "|".join(str(x) for x in old_display_lines_offscreen), + ) + logger.debug( + " display_lines_offscreen %s", + "|".join(str(x) for x in display_lines_offscreen), + ) + if ( + old_display_lines_offscreen[: len(display_lines_offscreen)] + != display_lines_offscreen + ) and not self.history_already_messed_up: self.inconsistent_history = True - logger.debug('after rewind, self.inconsistent_history is %r', - self.inconsistent_history) + logger.debug( + "after rewind, self.inconsistent_history is %r", + self.inconsistent_history, + ) self._cursor_offset = 0 - self.current_line = '' + self.current_line = "" def initialize_interp(self): - self.coderunner.interp.locals['_repl'] = self + self.coderunner.interp.locals["_repl"] = self self.coderunner.interp.runsource( - 'from bpython.curtsiesfrontend._internal ' - 'import _Helper') - self.coderunner.interp.runsource('help = _Helper(_repl)\n') + "from bpython.curtsiesfrontend._internal " "import _Helper" + ) + self.coderunner.interp.runsource("help = _Helper(_repl)\n") - del self.coderunner.interp.locals['_repl'] - del self.coderunner.interp.locals['_Helper'] + del self.coderunner.interp.locals["_repl"] + del self.coderunner.interp.locals["_Helper"] def getstdout(self): lines = self.lines_for_display + [self.current_line_formatted] - s = '\n'.join(x.s if isinstance(x, FmtStr) else x for x in lines) \ - if lines else '' + s = ( + "\n".join(x.s if isinstance(x, FmtStr) else x for x in lines) + if lines + else "" + ) return s def focus_on_subprocess(self, args): @@ -1688,10 +1892,12 @@ def focus_on_subprocess(self, args): sys.__stdout__.write(terminal.save) sys.__stdout__.write(terminal.move(0, 0)) sys.__stdout__.flush() - p = subprocess.Popen(args, - stdin=self.orig_stdin, - stderr=sys.__stderr__, - stdout=sys.__stdout__) + p = subprocess.Popen( + args, + stdin=self.orig_stdin, + stderr=sys.__stderr__, + stdout=sys.__stdout__, + ) p.wait() sys.__stdout__.write(terminal.restore) sys.__stdout__.flush() @@ -1712,56 +1918,72 @@ def show_source(self): try: source = self.get_source_of_current_name() except SourceNotFound as e: - self.status_bar.message('%s' % (e, )) + self.status_bar.message("%s" % (e,)) else: if self.config.highlight_show_source: - source = format(PythonLexer().get_tokens(source), - TerminalFormatter()) + source = format( + PythonLexer().get_tokens(source), TerminalFormatter() + ) self.pager(source) def help_text(self): - return self.version_help_text() + '\n' + self.key_help_text() + return self.version_help_text() + "\n" + self.key_help_text() def version_help_text(self): - return (('bpython-curtsies version %s' % bpython.__version__) + ' ' + - ('using curtsies version %s' % curtsies.__version__) + '\n' + - HELP_MESSAGE.format(config_file_location=default_config_path(), - example_config_url=EXAMPLE_CONFIG_URL, - config=self.config)) + return ( + ("bpython-curtsies version %s" % bpython.__version__) + + " " + + ("using curtsies version %s" % curtsies.__version__) + + "\n" + + HELP_MESSAGE.format( + config_file_location=default_config_path(), + example_config_url=EXAMPLE_CONFIG_URL, + config=self.config, + ) + ) def key_help_text(self): - NOT_IMPLEMENTED = ('suspend', 'cut to buffer', 'search', 'last output', - 'yank from buffer', 'cut to buffer') + NOT_IMPLEMENTED = ( + "suspend", + "cut to buffer", + "search", + "last output", + "yank from buffer", + "cut to buffer", + ) pairs = [] - pairs.append(['complete history suggestion', - 'right arrow at end of line']) - pairs.append(['previous match with current line', 'up arrow']) - for functionality, key in [(attr[:-4].replace('_', ' '), - getattr(self.config, attr)) - for attr in self.config.__dict__ - if attr.endswith('key')]: + pairs.append( + ["complete history suggestion", "right arrow at end of line"] + ) + pairs.append(["previous match with current line", "up arrow"]) + for functionality, key in [ + (attr[:-4].replace("_", " "), getattr(self.config, attr)) + for attr in self.config.__dict__ + if attr.endswith("key") + ]: if functionality in NOT_IMPLEMENTED: - key = 'Not Implemented' - if key == '': - key = 'Disabled' + key = "Not Implemented" + if key == "": + key = "Disabled" pairs.append([functionality, key]) max_func = max(len(func) for func, key in pairs) - return '\n'.join('%s : %s' % (func.rjust(max_func), key) - for func, key in pairs) + return "\n".join( + "%s : %s" % (func.rjust(max_func), key) for func, key in pairs + ) def is_nop(char): - return unicodedata.category(unicode(char)) == 'Cc' + return unicodedata.category(unicode(char)) == "Cc" def tabs_to_spaces(line): - return line.replace('\t', ' ') + return line.replace("\t", " ") def _last_word(line): - return line.split().pop() if line.split() else '' + return line.split().pop() if line.split() else "" def compress_paste_event(paste_event): @@ -1788,11 +2010,11 @@ def just_simple_events(event_list): for e in event_list: # '\n' necessary for pastes if e in ("", "", "", "\n", "\r"): - simple_events.append('\n') + simple_events.append("\n") elif isinstance(e, events.Event): pass # ignore events - elif e == '': - simple_events.append(' ') + elif e == "": + simple_events.append(" ") elif len(e) > 1: pass # get rid of etc. else: diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 5dd7d92ea..b1fdf3a81 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -11,6 +11,7 @@ from bpython.curtsiesfrontend.parse import func_for_letter from bpython._py3compat import py3 + if not py3: import inspect @@ -25,11 +26,17 @@ def display_linize(msg, columns, blank_line=False): """Returns lines obtained by splitting msg over multiple lines. Warning: if msg is empty, returns an empty list of lines""" - display_lines = ([msg[start:end] - for start, end in zip( - range(0, len(msg), columns), - range(columns, len(msg) + columns, columns))] - if msg else ([''] if blank_line else [])) + display_lines = ( + [ + msg[start:end] + for start, end in zip( + range(0, len(msg), columns), + range(columns, len(msg) + columns, columns), + ) + ] + if msg + else ([""] if blank_line else []) + ) return display_lines @@ -38,8 +45,8 @@ def paint_history(rows, columns, display_lines): for r, line in zip(range(rows), display_lines[-rows:]): lines.append(fmtstr(line[:columns])) r = fsarray(lines, width=columns) - assert r.shape[0] <= rows, repr(r.shape) + ' ' + repr(rows) - assert r.shape[1] <= columns, repr(r.shape) + ' ' + repr(columns) + assert r.shape[0] <= rows, repr(r.shape) + " " + repr(rows) + assert r.shape[1] <= columns, repr(r.shape) + " " + repr(columns) return r @@ -53,15 +60,15 @@ def paginate(rows, matches, current, words_wide): current = matches[0] per_page = rows * words_wide current_page = matches.index(current) // per_page - return matches[per_page * current_page:per_page * (current_page + 1)] + return matches[per_page * current_page : per_page * (current_page + 1)] def matches_lines(rows, columns, matches, current, config, match_format): - highlight_color = func_for_letter(config.color_scheme['operator'].lower()) + highlight_color = func_for_letter(config.color_scheme["operator"].lower()) if not matches: return [] - color = func_for_letter(config.color_scheme['main']) + color = func_for_letter(config.color_scheme["main"]) max_match_width = max(len(m) for m in matches) words_wide = max(1, (columns - 1) // (max_match_width + 1)) matches = [match_format(m) for m in matches] @@ -70,15 +77,18 @@ def matches_lines(rows, columns, matches, current, config, match_format): matches = paginate(rows, matches, current, words_wide) - result = [fmtstr(' ').join(color(m.ljust(max_match_width)) - if m != current - else highlight_color( - m.ljust(max_match_width)) - for m in matches[i:i + words_wide]) - for i in range(0, len(matches), words_wide)] - - logger.debug('match: %r' % current) - logger.debug('matches_lines: %r' % result) + result = [ + fmtstr(" ").join( + color(m.ljust(max_match_width)) + if m != current + else highlight_color(m.ljust(max_match_width)) + for m in matches[i : i + words_wide] + ) + for i in range(0, len(matches), words_wide) + ] + + logger.debug("match: %r" % current) + logger.debug("matches_lines: %r" % result) return result @@ -94,14 +104,16 @@ def formatted_argspec(funcprops, arg_pos, columns, config): kwonly = funcprops.argspec.kwonly kwonly_defaults = funcprops.argspec.kwonly_defaults or dict() - arg_color = func_for_letter(config.color_scheme['name']) - func_color = func_for_letter(config.color_scheme['name'].swapcase()) - punctuation_color = func_for_letter(config.color_scheme['punctuation']) - token_color = func_for_letter(config.color_scheme['token']) - bolds = {token_color: lambda x: bold(token_color(x)), - arg_color: lambda x: bold(arg_color(x))} + arg_color = func_for_letter(config.color_scheme["name"]) + func_color = func_for_letter(config.color_scheme["name"].swapcase()) + punctuation_color = func_for_letter(config.color_scheme["punctuation"]) + token_color = func_for_letter(config.color_scheme["token"]) + bolds = { + token_color: lambda x: bold(token_color(x)), + arg_color: lambda x: bold(arg_color(x)), + } - s = func_color(func) + arg_color(': (') + s = func_color(func) + arg_color(": (") if is_bound_method and isinstance(arg_pos, int): # TODO what values could this have? @@ -121,95 +133,130 @@ def formatted_argspec(funcprops, arg_pos, columns, config): s += color(arg) if kw is not None: - s += punctuation_color('=') + s += punctuation_color("=") if not py3: - kw = kw.decode('ascii', 'replace') + kw = kw.decode("ascii", "replace") s += token_color(kw) if i != len(args) - 1: - s += punctuation_color(', ') + s += punctuation_color(", ") if _args: if args: - s += punctuation_color(', ') - s += token_color('*%s' % (_args,)) + s += punctuation_color(", ") + s += token_color("*%s" % (_args,)) if py3 and kwonly: if not _args: if args: - s += punctuation_color(', ') - s += punctuation_color('*') + s += punctuation_color(", ") + s += punctuation_color("*") marker = object() for arg in kwonly: - s += punctuation_color(', ') + s += punctuation_color(", ") color = token_color if arg_pos: color = bolds[color] s += color(arg) default = kwonly_defaults.get(arg, marker) if default is not marker: - s += punctuation_color('=') + s += punctuation_color("=") s += token_color(repr(default)) if _kwargs: if args or _args or (py3 and kwonly): - s += punctuation_color(', ') - s += token_color('**%s' % (_kwargs,)) - s += punctuation_color(')') + s += punctuation_color(", ") + s += token_color("**%s" % (_kwargs,)) + s += punctuation_color(")") return linesplit(s, columns) def formatted_docstring(docstring, columns, config): if isinstance(docstring, bytes): - docstring = docstring.decode('utf8') + docstring = docstring.decode("utf8") elif isinstance(docstring, str if py3 else unicode): pass else: # TODO: fail properly here and catch possible exceptions in callers. return [] - color = func_for_letter(config.color_scheme['comment']) - return sum(([color(x) for x in (display_linize(line, columns) if line else - fmtstr(''))] - for line in docstring.split('\n')), []) - - -def paint_infobox(rows, columns, matches, funcprops, arg_pos, match, docstring, - config, match_format): + color = func_for_letter(config.color_scheme["comment"]) + return sum( + ( + [ + color(x) + for x in (display_linize(line, columns) if line else fmtstr("")) + ] + for line in docstring.split("\n") + ), + [], + ) + + +def paint_infobox( + rows, + columns, + matches, + funcprops, + arg_pos, + match, + docstring, + config, + match_format, +): """Returns painted completions, funcprops, match, docstring etc.""" if not (rows and columns): return FSArray(0, 0) width = columns - 4 - from_argspec = (formatted_argspec(funcprops, arg_pos, width, config) - if funcprops else []) - from_doc = (formatted_docstring(docstring, width, config) - if docstring else []) - from_matches = (matches_lines(max(1, rows - len(from_argspec) - 2), - width, matches, match, config, match_format) - if matches else []) + from_argspec = ( + formatted_argspec(funcprops, arg_pos, width, config) + if funcprops + else [] + ) + from_doc = ( + formatted_docstring(docstring, width, config) if docstring else [] + ) + from_matches = ( + matches_lines( + max(1, rows - len(from_argspec) - 2), + width, + matches, + match, + config, + match_format, + ) + if matches + else [] + ) lines = from_argspec + from_matches + from_doc def add_border(line): """Add colored borders left and right to a line.""" - new_line = border_color(config.left_border + ' ') + new_line = border_color(config.left_border + " ") new_line += line.ljust(width)[:width] - new_line += border_color(' ' + config.right_border) + new_line += border_color(" " + config.right_border) return new_line - border_color = func_for_letter(config.color_scheme['main']) - - top_line = border_color(config.left_top_corner + - config.top_border * (width + 2) + - config.right_top_corner) - bottom_line = border_color(config.left_bottom_corner + - config.bottom_border * (width + 2) + - config.right_bottom_corner) - - output_lines = list(itertools.chain((top_line, ), map(add_border, lines), - (bottom_line, ))) - r = fsarray(output_lines[:min(rows - 1, - len(output_lines) - 1)] + output_lines[-1:]) + border_color = func_for_letter(config.color_scheme["main"]) + + top_line = border_color( + config.left_top_corner + + config.top_border * (width + 2) + + config.right_top_corner + ) + bottom_line = border_color( + config.left_bottom_corner + + config.bottom_border * (width + 2) + + config.right_bottom_corner + ) + + output_lines = list( + itertools.chain((top_line,), map(add_border, lines), (bottom_line,)) + ) + r = fsarray( + output_lines[: min(rows - 1, len(output_lines) - 1)] + output_lines[-1:] + ) return r @@ -218,17 +265,25 @@ def paint_last_events(rows, columns, names, config): return fsarray([]) width = min(max(len(name) for name in names), columns - 2) output_lines = [] - output_lines.append(config.left_top_corner + config.top_border * width + - config.right_top_corner) - for name in reversed(names[max(0, len(names) - (rows - 2)):]): - output_lines.append(config.left_border + name[:width].center(width) + - config.right_border) - output_lines.append(config.left_bottom_corner + - config.bottom_border * width + - config.right_bottom_corner) + output_lines.append( + config.left_top_corner + + config.top_border * width + + config.right_top_corner + ) + for name in reversed(names[max(0, len(names) - (rows - 2)) :]): + output_lines.append( + config.left_border + + name[:width].center(width) + + config.right_border + ) + output_lines.append( + config.left_bottom_corner + + config.bottom_border * width + + config.right_bottom_corner + ) return fsarray(output_lines) def paint_statusbar(rows, columns, msg, config): - func = func_for_letter(config.color_scheme['main']) + func = func_for_letter(config.color_scheme["main"]) return fsarray([func(msg.ljust(columns))[:columns]]) diff --git a/bpython/curtsiesfrontend/sitefix.py b/bpython/curtsiesfrontend/sitefix.py index fa3016617..23795a04b 100644 --- a/bpython/curtsiesfrontend/sitefix.py +++ b/bpython/curtsiesfrontend/sitefix.py @@ -9,12 +9,14 @@ def resetquit(builtins): """Redefine builtins 'quit' and 'exit' not so close stdin """ + def __call__(self, code=None): raise SystemExit(code) - __call__.__name__ = 'FakeQuitCall' + + __call__.__name__ = "FakeQuitCall" builtins.quit.__class__.__call__ = __call__ def monkeypatch_quit(): - if 'site' in sys.modules: + if "site" in sys.modules: resetquit(builtins) diff --git a/bpython/filelock.py b/bpython/filelock.py index 703538738..1e6e17087 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -27,6 +27,7 @@ try: import fcntl import errno + has_fcntl = True except ImportError: has_fcntl = False @@ -34,6 +35,7 @@ try: import msvcrt import os + has_msvcrt = True except ImportError: has_msvcrt = False @@ -100,7 +102,9 @@ def __init__(self, fileobj, mode=None, filename=None): def acquire(self): # create a lock file and lock it - self.fileobj = os.open(self.filename, os.O_RDWR | os.O_CREAT | os.O_TRUNC) + self.fileobj = os.open( + self.filename, os.O_RDWR | os.O_CREAT | os.O_TRUNC + ) msvcrt.locking(self.fileobj, msvcrt.LK_NBLCK, 1) self.locked = True diff --git a/bpython/formatter.py b/bpython/formatter.py index 23bf0ccbd..54b1a264b 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -29,8 +29,19 @@ from __future__ import absolute_import from pygments.formatter import Formatter -from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Token, Whitespace, Literal, Punctuation +from pygments.token import ( + Keyword, + Name, + Comment, + String, + Error, + Number, + Operator, + Token, + Whitespace, + Literal, + Punctuation, +) from six import iteritems """These format strings are pretty ugly. @@ -60,20 +71,21 @@ Parenthesis = Token.Punctuation.Parenthesis theme_map = { - Keyword: 'keyword', - Name: 'name', - Comment: 'comment', - String: 'string', - Literal: 'string', - Error: 'error', - Number: 'number', - Token.Literal.Number.Float: 'number', - Operator: 'operator', - Punctuation: 'punctuation', - Token: 'token', - Whitespace: 'background', - Parenthesis: 'paren', - Parenthesis.UnderCursor: 'operator'} + Keyword: "keyword", + Name: "name", + Comment: "comment", + String: "string", + Literal: "string", + Error: "error", + Number: "number", + Token.Literal.Number.Float: "number", + Operator: "operator", + Punctuation: "punctuation", + Token: "token", + Whitespace: "background", + Parenthesis: "paren", + Parenthesis.UnderCursor: "operator", +} class BPythonFormatter(Formatter): @@ -91,17 +103,17 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} for k, v in iteritems(theme_map): - self.f_strings[k] = '\x01%s' % (color_scheme[v],) + self.f_strings[k] = "\x01%s" % (color_scheme[v],) if k is Parenthesis: # FIXME: Find a way to make this the inverse of the current # background colour - self.f_strings[k] += 'I' + self.f_strings[k] += "I" super(BPythonFormatter, self).__init__(**options) def format(self, tokensource, outfile): - o = '' + o = "" for token, text in tokensource: - if text == '\n': + if text == "\n": continue while token not in self.f_strings: @@ -109,4 +121,5 @@ def format(self, tokensource, outfile): o += "%s\x03%s\x04" % (self.f_strings[token], text) outfile.write(o.rstrip()) + # vim: sw=4 ts=4 sts=4 ai et diff --git a/bpython/history.py b/bpython/history.py index edcf8b500..f5c83aa42 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -39,14 +39,14 @@ class History(object): def __init__(self, entries=None, duplicates=True, hist_size=100): if entries is None: - self.entries = [''] + self.entries = [""] else: self.entries = list(entries) # how many lines back in history is currently selected where 0 is the # saved typed line, 1 the prev entered line self.index = 0 # what was on the prompt before using history - self.saved_line = '' + self.saved_line = "" self.duplicates = duplicates self.hist_size = hist_size @@ -54,7 +54,7 @@ def append(self, line): self.append_to(self.entries, line) def append_to(self, entries, line): - line = line.rstrip('\n') + line = line.rstrip("\n") if line: if not self.duplicates: # remove duplicates @@ -71,15 +71,17 @@ def first(self): self.index = len(self.entries) return self.entries[-self.index] - def back(self, start=True, search=False, target=None, - include_current=False): + def back( + self, start=True, search=False, target=None, include_current=False + ): """Move one step back in the history.""" if target is None: target = self.saved_line if not self.is_at_end: if search: - self.index += self.find_partial_match_backward(target, - include_current) + self.index += self.find_partial_match_backward( + target, include_current + ) elif start: self.index += self.find_match_backward(target, include_current) else: @@ -111,15 +113,17 @@ def find_partial_match_backward(self, search_term, include_current=False): return idx + add return 0 - def forward(self, start=True, search=False, target=None, - include_current=False): + def forward( + self, start=True, search=False, target=None, include_current=False + ): """Move one step forward in the history.""" if target is None: target = self.saved_line if self.index > 1: if search: - self.index -= self.find_partial_match_forward(target, - include_current) + self.index -= self.find_partial_match_forward( + target, include_current + ) elif start: self.index -= self.find_match_forward(target, include_current) else: @@ -167,11 +171,12 @@ def enter(self, line): def reset(self): self.index = 0 - self.saved_line = '' + self.saved_line = "" def load(self, filename, encoding): - with io.open(filename, 'r', encoding=encoding, - errors='ignore') as hfile: + with io.open( + filename, "r", encoding=encoding, errors="ignore" + ) as hfile: with FileLock(hfile, filename=filename): self.entries = self.load_from(hfile) @@ -179,13 +184,15 @@ def load_from(self, fd): entries = [] for line in fd: self.append_to(entries, line) - return entries if len(entries) else [''] + return entries if len(entries) else [""] def save(self, filename, encoding, lines=0): - fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, - stat.S_IRUSR | stat.S_IWUSR) - with io.open(fd, 'w', encoding=encoding, - errors='ignore') as hfile: + fd = os.open( + filename, + os.O_WRONLY | os.O_CREAT | os.O_TRUNC, + stat.S_IRUSR | stat.S_IWUSR, + ) + with io.open(fd, "w", encoding=encoding, errors="ignore") as hfile: with FileLock(hfile, filename=filename): self.save_to(hfile, self.entries, lines) @@ -194,17 +201,19 @@ def save_to(self, fd, entries=None, lines=0): entries = self.entries for line in entries[-lines:]: fd.write(line) - fd.write('\n') + fd.write("\n") def append_reload_and_write(self, s, filename, encoding): if not self.hist_size: return self.append(s) try: - fd = os.open(filename, os.O_APPEND | os.O_RDWR | os.O_CREAT, - stat.S_IRUSR | stat.S_IWUSR) - with io.open(fd, 'a+', encoding=encoding, - errors='ignore') as hfile: + fd = os.open( + filename, + os.O_APPEND | os.O_RDWR | os.O_CREAT, + stat.S_IRUSR | stat.S_IWUSR, + ) + with io.open(fd, "a+", encoding=encoding, errors="ignore") as hfile: with FileLock(hfile, filename=filename): # read entries hfile.seek(0, os.SEEK_SET) @@ -219,10 +228,11 @@ def append_reload_and_write(self, s, filename, encoding): self.entries = entries except EnvironmentError as err: raise RuntimeError( - _('Error occurred while writing to file %s (%s)') - % (filename, err.strerror)) + _("Error occurred while writing to file %s (%s)") + % (filename, err.strerror) + ) else: if len(self.entries) == 0: # Make sure that entries contains at least one element. If the # file and s are empty, this can occur. - self.entries = [''] + self.entries = [""] diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 0a243ec98..6dea46125 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -25,8 +25,12 @@ from __future__ import absolute_import from ._py3compat import py3, try_decode -from .line import (current_word, current_import, current_from_import_from, - current_from_import_import) +from .line import ( + current_word, + current_import, + current_from_import_from, + current_from_import_import, +) import imp import os @@ -36,6 +40,7 @@ if py3: import importlib.machinery + SUFFIXES = importlib.machinery.all_suffixes() else: SUFFIXES = [suffix for suffix, mode, type in imp.get_suffixes()] @@ -45,49 +50,58 @@ fully_loaded = False -def module_matches(cw, prefix=''): +def module_matches(cw, prefix=""): """Modules names to replace cw with""" - full = '%s.%s' % (prefix, cw) if prefix else cw - matches = (name for name in modules - if (name.startswith(full) and - name.find('.', len(full)) == -1)) + full = "%s.%s" % (prefix, cw) if prefix else cw + matches = ( + name + for name in modules + if (name.startswith(full) and name.find(".", len(full)) == -1) + ) if prefix: - return set(match[len(prefix)+1:] for match in matches) + return set(match[len(prefix) + 1 :] for match in matches) else: return set(matches) -def attr_matches(cw, prefix='', only_modules=False): +def attr_matches(cw, prefix="", only_modules=False): """Attributes to replace name with""" - full = '%s.%s' % (prefix, cw) if prefix else cw - module_name, _, name_after_dot = full.rpartition('.') + full = "%s.%s" % (prefix, cw) if prefix else cw + module_name, _, name_after_dot = full.rpartition(".") if module_name not in sys.modules: return set() module = sys.modules[module_name] if only_modules: - matches = (name for name in dir(module) - if (name.startswith(name_after_dot) and - '%s.%s' % (module_name, name)) in sys.modules) + matches = ( + name + for name in dir(module) + if ( + name.startswith(name_after_dot) + and "%s.%s" % (module_name, name) + ) + in sys.modules + ) else: - matches = (name for name in dir(module) - if name.startswith(name_after_dot)) - module_part, _, _ = cw.rpartition('.') + matches = ( + name for name in dir(module) if name.startswith(name_after_dot) + ) + module_part, _, _ = cw.rpartition(".") if module_part: - matches = ('%s.%s' % (module_part, m) for m in matches) + matches = ("%s.%s" % (module_part, m) for m in matches) - generator = (try_decode(match, 'ascii') for match in matches) + generator = (try_decode(match, "ascii") for match in matches) return set(filter(lambda x: x is not None, generator)) def module_attr_matches(name): """Only attributes which are modules to replace name with""" - return attr_matches(name, prefix='', only_modules=True) + return attr_matches(name, prefix="", only_modules=True) def complete(cursor_offset, line): """Construct a full list of possibly completions for imports.""" tokens = line.split() - if 'from' not in tokens and 'import' not in tokens: + if "from" not in tokens and "import" not in tokens: return None result = current_word(cursor_offset, line) @@ -100,8 +114,7 @@ def complete(cursor_offset, line): if import_import is not None: # `from a import ` completion matches = module_matches(import_import[2], from_import_from[2]) - matches.update(attr_matches(import_import[2], - from_import_from[2])) + matches.update(attr_matches(import_import[2], from_import_from[2])) else: # `from ` completion matches = module_attr_matches(from_import_from[2]) @@ -131,7 +144,7 @@ def find_modules(path): for name in filenames: if not any(name.endswith(suffix) for suffix in SUFFIXES): # Possibly a package - if '.' in name: + if "." in name: continue elif os.path.isdir(os.path.join(path, name)): # Unfortunately, CPython just crashes if there is a directory @@ -139,7 +152,7 @@ def find_modules(path): continue for suffix in SUFFIXES: if name.endswith(suffix): - name = name[:-len(suffix)] + name = name[: -len(suffix)] break if py3 and name == "badsyntax_pep3120": # Workaround for issue #166 @@ -160,8 +173,8 @@ def find_modules(path): else: # Yay, package for subname in find_modules(pathname): - if subname != '__init__': - yield '%s.%s' % (name, subname) + if subname != "__init__": + yield "%s.%s" % (name, subname) yield name @@ -169,15 +182,14 @@ def find_all_modules(path=None): """Return a list with all modules in `path`, which should be a list of directory names. If path is not given, sys.path will be used.""" if path is None: - modules.update(try_decode(m, 'ascii') - for m in sys.builtin_module_names) + modules.update(try_decode(m, "ascii") for m in sys.builtin_module_names) path = sys.path for p in path: if not p: p = os.curdir for module in find_modules(p): - module = try_decode(module, 'ascii') + module = try_decode(module, "ascii") if module is None: continue modules.add(module) diff --git a/bpython/inspection.py b/bpython/inspection.py index 49423e45a..295a8a61f 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -40,12 +40,22 @@ if not py3: import types - _name = LazyReCompile(r'[a-zA-Z_]\w*$') + _name = LazyReCompile(r"[a-zA-Z_]\w*$") -ArgSpec = namedtuple('ArgSpec', ['args', 'varargs', 'varkwargs', 'defaults', - 'kwonly', 'kwonly_defaults', 'annotations']) +ArgSpec = namedtuple( + "ArgSpec", + [ + "args", + "varargs", + "varkwargs", + "defaults", + "kwonly", + "kwonly_defaults", + "annotations", + ], +) -FuncProps = namedtuple('FuncProps', ['func', 'argspec', 'is_bound_method']) +FuncProps = namedtuple("FuncProps", ["func", "argspec", "is_bound_method"]) class AttrCleaner(object): @@ -71,16 +81,16 @@ def __enter__(self): # The upshot being that introspecting on an object to display its # attributes will avoid unwanted side-effects. if is_new_style(self.obj): - __getattr__ = getattr(type_, '__getattr__', None) + __getattr__ = getattr(type_, "__getattr__", None) if __getattr__ is not None: try: - setattr(type_, '__getattr__', (lambda *_, **__: None)) + setattr(type_, "__getattr__", (lambda *_, **__: None)) except TypeError: __getattr__ = None - __getattribute__ = getattr(type_, '__getattribute__', None) + __getattribute__ = getattr(type_, "__getattribute__", None) if __getattribute__ is not None: try: - setattr(type_, '__getattribute__', object.__getattribute__) + setattr(type_, "__getattribute__", object.__getattribute__) except TypeError: # XXX: This happens for e.g. built-in types __getattribute__ = None @@ -93,16 +103,20 @@ def __exit__(self, exc_type, exc_val, exc_tb): __getattribute__, __getattr__ = self.attribs # Dark magic: if __getattribute__ is not None: - setattr(type_, '__getattribute__', __getattribute__) + setattr(type_, "__getattribute__", __getattribute__) if __getattr__ is not None: - setattr(type_, '__getattr__', __getattr__) + setattr(type_, "__getattr__", __getattr__) # /Dark magic if py3: + def is_new_style(obj): return True + + else: + def is_new_style(obj): """Returns True if obj is a new-style class or object""" return type(obj) not in [types.InstanceType, types.ClassType] @@ -135,15 +149,16 @@ def parsekeywordpairs(signature): continue if token is Token.Punctuation: - if value in [u'(', u'{', u'[']: + if value in [u"(", u"{", u"["]: parendepth += 1 - elif value in [u')', u'}', u']']: + elif value in [u")", u"}", u"]"]: parendepth -= 1 - elif value == ':' and parendepth == -1: + elif value == ":" and parendepth == -1: # End of signature reached break - if ((value == ',' and parendepth == 0) or - (value == ')' and parendepth == -1)): + if (value == "," and parendepth == 0) or ( + value == ")" and parendepth == -1 + ): stack.append(substack) substack = [] continue @@ -154,7 +169,7 @@ def parsekeywordpairs(signature): d = {} for item in stack: if len(item) >= 3: - d[item[0]] = ''.join(item[2:]) + d[item[0]] = "".join(item[2:]) return d @@ -169,14 +184,14 @@ def fixlongargs(f, argspec): values = list(argspec[3]) if not values: return - keys = argspec[0][-len(values):] + keys = argspec[0][-len(values) :] try: src = inspect.getsourcelines(f) except (IOError, IndexError): # IndexError is raised in inspect.findsource(), can happen in # some situations. See issue #94. return - signature = ''.join(src[0]) + signature = "".join(src[0]) kwparsed = parsekeywordpairs(signature) for i, (key, value) in enumerate(zip(keys, values)): @@ -186,7 +201,7 @@ def fixlongargs(f, argspec): argspec[3] = values -getpydocspec_re = LazyReCompile(r'([a-zA-Z_][a-zA-Z0-9_]*?)\((.*?)\)') +getpydocspec_re = LazyReCompile(r"([a-zA-Z_][a-zA-Z0-9_]*?)\((.*?)\)") def getpydocspec(f, func): @@ -199,7 +214,7 @@ def getpydocspec(f, func): if s is None: return None - if not hasattr(f, '__name__') or s.groups()[0] != f.__name__: + if not hasattr(f, "__name__") or s.groups()[0] != f.__name__: return None args = list() @@ -207,14 +222,14 @@ def getpydocspec(f, func): varargs = varkwargs = None kwonly_args = list() kwonly_defaults = dict() - for arg in s.group(2).split(','): + for arg in s.group(2).split(","): arg = arg.strip() - if arg.startswith('**'): + if arg.startswith("**"): varkwargs = arg[2:] - elif arg.startswith('*'): + elif arg.startswith("*"): varargs = arg[1:] else: - arg, _, default = arg.partition('=') + arg, _, default = arg.partition("=") if varargs is not None: kwonly_args.append(arg) if default: @@ -224,8 +239,9 @@ def getpydocspec(f, func): if default: defaults.append(default) - return ArgSpec(args, varargs, varkwargs, defaults, kwonly_args, - kwonly_defaults, None) + return ArgSpec( + args, varargs, varkwargs, defaults, kwonly_args, kwonly_defaults, None + ) def getfuncprops(func, f): @@ -233,17 +249,17 @@ def getfuncprops(func, f): # (i.e. FooClass(...) and not FooClass.__init__(...) -- the former would # not take 'self', the latter would: try: - func_name = getattr(f, '__name__', None) + func_name = getattr(f, "__name__", None) except: # if calling foo.__name__ would result in an error func_name = None try: - is_bound_method = ((inspect.ismethod(f) and f.__self__ is not None) or - (func_name == '__init__' and not - func.endswith('.__init__')) or - (func_name == '__new__' and not - func.endswith('.__new__'))) + is_bound_method = ( + (inspect.ismethod(f) and f.__self__ is not None) + or (func_name == "__init__" and not func.endswith(".__init__")) + or (func_name == "__new__" and not func.endswith(".__new__")) + ) except: # if f is a method from a xmlrpclib.Server instance, func_name == # '__init__' throws xmlrpclib.Fault (see #202) @@ -265,18 +281,22 @@ def getfuncprops(func, f): if argspec is None: return None if inspect.ismethoddescriptor(f): - argspec.args.insert(0, 'obj') + argspec.args.insert(0, "obj") fprops = FuncProps(func, argspec, is_bound_method) return fprops def is_eval_safe_name(string): if py3: - return all(part.isidentifier() and not keyword.iskeyword(part) - for part in string.split('.')) + return all( + part.isidentifier() and not keyword.iskeyword(part) + for part in string.split(".") + ) else: - return all(_name.match(part) and not keyword.iskeyword(part) - for part in string.split('.')) + return all( + _name.match(part) and not keyword.iskeyword(part) + for part in string.split(".") + ) def is_callable(obj): @@ -329,10 +349,18 @@ def get_argspec_from_signature(f): if not annotations: annotations = None - return [args, varargs, varkwargs, defaults, kwonly, kwonly_defaults, annotations] + return [ + args, + varargs, + varkwargs, + defaults, + kwonly, + kwonly_defaults, + annotations, + ] -get_encoding_line_re = LazyReCompile(r'^.*coding[:=]\s*([-\w.]+).*$') +get_encoding_line_re = LazyReCompile(r"^.*coding[:=]\s*([-\w.]+).*$") def get_encoding(obj): @@ -341,7 +369,7 @@ def get_encoding(obj): m = get_encoding_line_re.search(line) if m: return m.group(1) - return 'ascii' + return "ascii" def get_encoding_comment(source): @@ -355,20 +383,24 @@ def get_encoding_comment(source): def get_encoding_file(fname): """Try to obtain encoding information from a Python source file.""" - with io.open(fname, 'rt', encoding='ascii', errors='ignore') as f: + with io.open(fname, "rt", encoding="ascii", errors="ignore") as f: for unused in range(2): line = f.readline() match = get_encoding_line_re.search(line) if match: return match.group(1) - return 'ascii' + return "ascii" if py3: + def get_source_unicode(obj): """Returns a decoded source of object""" return inspect.getsource(obj) + + else: + def get_source_unicode(obj): """Returns a decoded source of object""" return inspect.getsource(obj).decode(get_encoding(obj)) diff --git a/bpython/keys.py b/bpython/keys.py index 00c25b792..0f5b9bc28 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -29,8 +29,7 @@ class KeyMap(object): - - def __init__(self, default=''): + def __init__(self, default=""): self.map = {} self.default = default @@ -41,8 +40,10 @@ def __getitem__(self, key): elif key in self.map: return self.map[key] else: - raise KeyError('Configured keymap (%s)' % key + - ' does not exist in bpython.keys') + raise KeyError( + "Configured keymap (%s)" % key + + " does not exist in bpython.keys" + ) def __delitem__(self, key): del self.map[key] @@ -52,27 +53,29 @@ def __setitem__(self, key, value): cli_key_dispatch = KeyMap(tuple()) -urwid_key_dispatch = KeyMap('') +urwid_key_dispatch = KeyMap("") # fill dispatch with letters for c in string.ascii_lowercase: - cli_key_dispatch['C-%s' % c] = (chr(string.ascii_lowercase.index(c) + 1), - '^%s' % c.upper()) + cli_key_dispatch["C-%s" % c] = ( + chr(string.ascii_lowercase.index(c) + 1), + "^%s" % c.upper(), + ) for c in string.ascii_lowercase: - urwid_key_dispatch['C-%s' % c] = 'ctrl %s' % c - urwid_key_dispatch['M-%s' % c] = 'meta %s' % c + urwid_key_dispatch["C-%s" % c] = "ctrl %s" % c + urwid_key_dispatch["M-%s" % c] = "meta %s" % c # fill dispatch with cool characters -cli_key_dispatch['C-['] = (chr(27), '^[') -cli_key_dispatch['C-\\'] = (chr(28), '^\\') -cli_key_dispatch['C-]'] = (chr(29), '^]') -cli_key_dispatch['C-^'] = (chr(30), '^^') -cli_key_dispatch['C-_'] = (chr(31), '^_') +cli_key_dispatch["C-["] = (chr(27), "^[") +cli_key_dispatch["C-\\"] = (chr(28), "^\\") +cli_key_dispatch["C-]"] = (chr(29), "^]") +cli_key_dispatch["C-^"] = (chr(30), "^^") +cli_key_dispatch["C-_"] = (chr(31), "^_") # fill dispatch with function keys for x in range(1, 13): - cli_key_dispatch['F%d' % x] = ('KEY_F(%d)' % x,) + cli_key_dispatch["F%d" % x] = ("KEY_F(%d)" % x,) for x in range(1, 13): - urwid_key_dispatch['F%d' % x] = 'f%d' % x + urwid_key_dispatch["F%d" % x] = "f%d" % x diff --git a/bpython/lazyre.py b/bpython/lazyre.py index fa1cbd6ef..7371ab042 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -43,6 +43,7 @@ def _impl(self, *args, **kwargs): if self.compiled is None: self.compiled = re.compile(self.regex, self.flags) return method(self, *args, **kwargs) + return _impl @compile_regex diff --git a/bpython/line.py b/bpython/line.py index 6456073f0..af54ab31b 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -12,11 +12,9 @@ from .lazyre import LazyReCompile -LinePart = namedtuple('LinePart', ['start', 'stop', 'word']) +LinePart = namedtuple("LinePart", ["start", "stop", "word"]) -current_word_re = LazyReCompile( - r'(?(?:""")|"|(?:''\')|')(?:((?P.+?)(?P=open))|''' - '''(?P.+))''') + """(?P.+))""" +) def current_string(cursor_offset, line): @@ -78,7 +77,7 @@ def current_string(cursor_offset, line): return None -current_object_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]') +current_object_re = LazyReCompile(r"([\w_][\w0-9_]*)[.]") def current_object(cursor_offset, line): @@ -89,18 +88,18 @@ def current_object(cursor_offset, line): return None start, end, word = match matches = current_object_re.finditer(word) - s = '' + s = "" for m in matches: if m.end(1) + start < cursor_offset: if s: - s += '.' + s += "." s += m.group(1) if not s: return None return LinePart(start, start + len(s), s) -current_object_attribute_re = LazyReCompile(r'([\w_][\w0-9_]*)[.]?') +current_object_attribute_re = LazyReCompile(r"([\w_][\w0-9_]*)[.]?") def current_object_attribute(cursor_offset, line): @@ -113,14 +112,17 @@ def current_object_attribute(cursor_offset, line): matches = current_object_attribute_re.finditer(word) next(matches) for m in matches: - if (m.start(1) + start <= cursor_offset and - m.end(1) + start >= cursor_offset): + if ( + m.start(1) + start <= cursor_offset + and m.end(1) + start >= cursor_offset + ): return LinePart(m.start(1) + start, m.end(1) + start, m.group(1)) return None current_from_import_from_re = LazyReCompile( - r'from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*') + r"from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*" +) def current_from_import_from(cursor_offset, line): @@ -131,19 +133,20 @@ def current_from_import_from(cursor_offset, line): """ # TODO allow for as's tokens = line.split() - if not ('from' in tokens or 'import' in tokens): + if not ("from" in tokens or "import" in tokens): return None matches = current_from_import_from_re.finditer(line) for m in matches: - if ((m.start(1) < cursor_offset and m.end(1) >= cursor_offset) or - (m.start(2) < cursor_offset and m.end(2) >= cursor_offset)): + if (m.start(1) < cursor_offset and m.end(1) >= cursor_offset) or ( + m.start(2) < cursor_offset and m.end(2) >= cursor_offset + ): return LinePart(m.start(1), m.end(1), m.group(1)) return None -current_from_import_import_re_1 = LazyReCompile(r'from\s([\w0-9_.]*)\s+import') -current_from_import_import_re_2 = LazyReCompile(r'([\w0-9_]+)') -current_from_import_import_re_3 = LazyReCompile(r'[,][ ]([\w0-9_]*)') +current_from_import_import_re_1 = LazyReCompile(r"from\s([\w0-9_.]*)\s+import") +current_from_import_import_re_2 = LazyReCompile(r"([\w0-9_]+)") +current_from_import_import_re_3 = LazyReCompile(r"[,][ ]([\w0-9_]*)") def current_from_import_import(cursor_offset, line): @@ -154,11 +157,11 @@ def current_from_import_import(cursor_offset, line): baseline = current_from_import_import_re_1.search(line) if baseline is None: return None - match1 = current_from_import_import_re_2.search(line[baseline.end():]) + match1 = current_from_import_import_re_2.search(line[baseline.end() :]) if match1 is None: return None - matches = current_from_import_import_re_3.finditer(line[baseline.end():]) - for m in chain((match1, ), matches): + matches = current_from_import_import_re_3.finditer(line[baseline.end() :]) + for m in chain((match1,), matches): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: @@ -166,9 +169,9 @@ def current_from_import_import(cursor_offset, line): return None -current_import_re_1 = LazyReCompile(r'import') -current_import_re_2 = LazyReCompile(r'([\w0-9_.]+)') -current_import_re_3 = LazyReCompile(r'[,][ ]([\w0-9_.]*)') +current_import_re_1 = LazyReCompile(r"import") +current_import_re_2 = LazyReCompile(r"([\w0-9_.]+)") +current_import_re_3 = LazyReCompile(r"[,][ ]([\w0-9_.]*)") def current_import(cursor_offset, line): @@ -176,11 +179,11 @@ def current_import(cursor_offset, line): baseline = current_import_re_1.search(line) if baseline is None: return None - match1 = current_import_re_2.search(line[baseline.end():]) + match1 = current_import_re_2.search(line[baseline.end() :]) if match1 is None: return None - matches = current_import_re_3.finditer(line[baseline.end():]) - for m in chain((match1, ), matches): + matches = current_import_re_3.finditer(line[baseline.end() :]) + for m in chain((match1,), matches): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: @@ -194,7 +197,7 @@ def current_method_definition_name(cursor_offset, line): """The name of a method being defined""" matches = current_method_definition_name_re.finditer(line) for m in matches: - if (m.start(1) <= cursor_offset and m.end(1) >= cursor_offset): + if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -217,12 +220,13 @@ def current_dotted_attribute(cursor_offset, line): if match is None: return None start, end, word = match - if '.' in word[1:]: + if "." in word[1:]: return LinePart(start, end, word) current_expression_attribute_re = LazyReCompile( - r'[.]\s*((?:[\w_][\w0-9_]*)|(?:))') + r"[.]\s*((?:[\w_][\w0-9_]*)|(?:))" +) def current_expression_attribute(cursor_offset, line): @@ -230,6 +234,6 @@ def current_expression_attribute(cursor_offset, line): # TODO replace with more general current_expression_attribute matches = current_expression_attribute_re.finditer(line) for m in matches: - if (m.start(1) <= cursor_offset and m.end(1) >= cursor_offset): + if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: return LinePart(m.start(1), m.end(1), m.group(1)) return None diff --git a/bpython/pager.py b/bpython/pager.py index 13e904a6b..2191a5f8d 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -35,14 +35,14 @@ from bpython._py3compat import py3 -def get_pager_command(default='less -rf'): - command = shlex.split(os.environ.get('PAGER', default)) +def get_pager_command(default="less -rf"): + command = shlex.split(os.environ.get("PAGER", default)) return command def page_internal(data): """A more than dumb pager function.""" - if hasattr(pydoc, 'ttypager'): + if hasattr(pydoc, "ttypager"): pydoc.ttypager(data) else: sys.stdout.write(data) @@ -57,7 +57,7 @@ def page(data, use_internal=False): try: popen = subprocess.Popen(command, stdin=subprocess.PIPE) if py3 or isinstance(data, unicode): - data = data.encode(sys.__stdout__.encoding, 'replace') + data = data.encode(sys.__stdout__.encoding, "replace") popen.stdin.write(data) popen.stdin.close() except OSError as e: @@ -78,4 +78,5 @@ def page(data, use_internal=False): break curses.doupdate() + # vim: sw=4 ts=4 sts=4 ai et diff --git a/bpython/paste.py b/bpython/paste.py index b76ab35c1..b68e9688b 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -49,12 +49,8 @@ def __init__(self, url, expiry, show_url, removal_url): def paste(self, s): """Upload to pastebin via json interface.""" - url = urljoin(self.url, '/json/new') - payload = { - 'code': s, - 'lexer': 'pycon', - 'expiry': self.expiry - } + url = urljoin(self.url, "/json/new") + payload = {"code": s, "lexer": "pycon", "expiry": self.expiry} try: response = requests.post(url, data=payload, verify=True) @@ -65,13 +61,14 @@ def paste(self, s): data = response.json() paste_url_template = Template(self.show_url) - paste_id = urlquote(data['paste_id']) + paste_id = urlquote(data["paste_id"]) paste_url = paste_url_template.safe_substitute(paste_id=paste_id) removal_url_template = Template(self.removal_url) - removal_id = urlquote(data['removal_id']) + removal_id = urlquote(data["removal_id"]) removal_url = removal_url_template.safe_substitute( - removal_id=removal_id) + removal_id=removal_id + ) return (paste_url, removal_url) @@ -84,31 +81,41 @@ def paste(self, s): """Call out to helper program for pastebin upload.""" try: - helper = subprocess.Popen('', - executable=self.executable, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE) + helper = subprocess.Popen( + "", + executable=self.executable, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) helper.stdin.write(s.encode(getpreferredencoding())) output = helper.communicate()[0].decode(getpreferredencoding()) paste_url = output.split()[0] except OSError as e: if e.errno == errno.ENOENT: - raise PasteFailed(_('Helper program not found.')) + raise PasteFailed(_("Helper program not found.")) else: - raise PasteFailed(_('Helper program could not be run.')) + raise PasteFailed(_("Helper program could not be run.")) if helper.returncode != 0: - raise PasteFailed(_('Helper program returned non-zero exit ' - 'status %d.' % (helper.returncode, ))) + raise PasteFailed( + _( + "Helper program returned non-zero exit " + "status %d." % (helper.returncode,) + ) + ) if not paste_url: - raise PasteFailed(_('No output from helper program.')) + raise PasteFailed(_("No output from helper program.")) else: parsed_url = urlparse(paste_url) - if (not parsed_url.scheme or - any(unicodedata.category(c) == 'Cc' - for c in paste_url)): - raise PasteFailed(_('Failed to recognize the helper ' - 'program\'s output as an URL.')) + if not parsed_url.scheme or any( + unicodedata.category(c) == "Cc" for c in paste_url + ): + raise PasteFailed( + _( + "Failed to recognize the helper " + "program's output as an URL." + ) + ) return paste_url, None diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index 5a0885395..d2b1396b5 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -15,7 +15,7 @@ def __init__(self, *args, **kwargs): def is_bpython_filename(self, fname): try: - return fname.startswith('' + return "" diff --git a/bpython/repl.py b/bpython/repl.py index d7f48a7b8..cfac71244 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -63,7 +63,7 @@ class RuntimeTimer(object): def __init__(self): self.reset_timer() - self.time = time.monotonic if hasattr(time, 'monotonic') else time.time + self.time = time.monotonic if hasattr(time, "monotonic") else time.time def __enter__(self): self.start = self.time() @@ -84,7 +84,7 @@ def estimate(self): class Interpreter(code.InteractiveInterpreter, object): """Source code interpreter for use in bpython.""" - bpython_input_re = LazyReCompile(r'') + bpython_input_re = LazyReCompile(r"") def __init__(self, locals=None, encoding=None): """Constructor. @@ -113,7 +113,7 @@ def __init__(self, locals=None, encoding=None): if locals is None: # instead of messing with sys.modules, we should modify sys.modules # in the interpreter instance - sys.modules['__main__'] = main_mod = ModuleType('__main__') + sys.modules["__main__"] = main_mod = ModuleType("__main__") locals = main_mod.__dict__ # Unfortunately code.InteractiveInterpreter is a classic class, so no @@ -124,8 +124,7 @@ def __init__(self, locals=None, encoding=None): def reset_running_time(self): self.running_time = 0 - def runsource(self, source, filename=None, symbol='single', - encode='auto'): + def runsource(self, source, filename=None, symbol="single", encode="auto"): """Execute Python code. source, filename and symbol are passed on to @@ -147,14 +146,13 @@ def runsource(self, source, filename=None, symbol='single', unicode string in Python 2 will throw a ValueError.""" # str means bytestring in Py2 if encode and not py3 and isinstance(source, unicode): - if encode != 'auto': + if encode != "auto": raise ValueError("can't add encoding line to unicode input") encode = False if encode and filename is not None: # files have encoding comments or implicit encoding of ASCII - if encode != 'auto': - raise ValueError( - "shouldn't add encoding line to file contents") + if encode != "auto": + raise ValueError("shouldn't add encoding line to file contents") encode = False if encode and not py3 and isinstance(source, str): @@ -165,21 +163,22 @@ def runsource(self, source, filename=None, symbol='single', # keep the existing encoding comment, but add two lines # because this interp always adds 2 to stack trace line # numbers in Python 2 - source = source.replace(comment, b'%s\n\n' % comment, 1) + source = source.replace(comment, b"%s\n\n" % comment, 1) else: - source = b'# coding: %s\n\n%s' % (self.encoding, source) + source = b"# coding: %s\n\n%s" % (self.encoding, source) elif not py3 and filename is None: # 2 blank lines still need to be added # because this interpreter always adds 2 to stack trace line # numbers in Python 2 when the filename is "" - newlines = u'\n\n' if isinstance(source, unicode) else b'\n\n' + newlines = u"\n\n" if isinstance(source, unicode) else b"\n\n" source = newlines + source # we know we're in Python 2 here, so ok to reference unicode if filename is None: filename = filename_for_console_input(source) with self.timer: - return code.InteractiveInterpreter.runsource(self, source, - filename, symbol) + return code.InteractiveInterpreter.runsource( + self, source, filename, symbol + ) def showsyntaxerror(self, filename=None): """Override the regular handler, the code's copied and pasted from @@ -202,8 +201,8 @@ def showsyntaxerror(self, filename=None): # Stuff in the right filename and right lineno # strip linecache line number if self.bpython_input_re.match(filename): - filename = '' - if filename == '' and not py3: + filename = "" + if filename == "" and not py3: lineno -= 2 value = SyntaxError(msg, (filename, lineno, offset, line)) sys.last_value = value @@ -225,16 +224,16 @@ def showtraceback(self): for i, (fname, lineno, module, something) in enumerate(tblist): # strip linecache line number if self.bpython_input_re.match(fname): - fname = '' + fname = "" tblist[i] = (fname, lineno, module, something) # Set the right lineno (encoding header adds an extra line) - if fname == '' and not py3: + if fname == "" and not py3: tblist[i] = (fname, lineno - 2, module, something) l = traceback.format_list(tblist) if l: l.insert(0, "Traceback (most recent call last):\n") - l[len(l):] = traceback.format_exception_only(t, v) + l[len(l) :] = traceback.format_exception_only(t, v) finally: pass @@ -258,7 +257,7 @@ class MatchesIterator(object): def __init__(self): # word being replaced in the original line of text - self.current_word = '' + self.current_word = "" # possible replacements for current_word self.matches = None # which word is currently replacing the current word @@ -288,7 +287,7 @@ def __iter__(self): def current(self): if self.index == -1: - raise ValueError('No current match.') + raise ValueError("No current match.") return self.matches[self.index] def next(self): @@ -312,14 +311,18 @@ def cur_line(self): def substitute(self, match): """Returns a cursor offset and line with match substituted in""" - start, end, word = self.completer.locate(self.orig_cursor_offset, - self.orig_line) - return (start + len(match), - self.orig_line[:start] + match + self.orig_line[end:]) + start, end, word = self.completer.locate( + self.orig_cursor_offset, self.orig_line + ) + return ( + start + len(match), + self.orig_line[:start] + match + self.orig_line[end:], + ) def is_cseq(self): return bool( - os.path.commonprefix(self.matches)[len(self.current_word):]) + os.path.commonprefix(self.matches)[len(self.current_word) :] + ) def substitute_cseq(self): """Returns a new line by substituting a common sequence in, and update @@ -329,8 +332,9 @@ def substitute_cseq(self): if len(self.matches) == 1: self.clear() else: - self.update(new_cursor_offset, new_line, self.matches, - self.completer) + self.update( + new_cursor_offset, new_line, self.matches, self.completer + ) if len(self.matches) == 1: self.clear() return new_cursor_offset, new_line @@ -350,13 +354,14 @@ def update(self, cursor_offset, current_line, matches, completer): self.completer = completer self.index = -1 self.start, self.end, self.current_word = self.completer.locate( - self.orig_cursor_offset, self.orig_line) + self.orig_cursor_offset, self.orig_line + ) def clear(self): self.matches = [] self.cursor_offset = -1 - self.current_line = '' - self.current_word = '' + self.current_line = "" + self.current_word = "" self.start = None self.end = None self.index = -1 @@ -425,13 +430,14 @@ def __init__(self, interp, config): """ self.config = config - self.cut_buffer = '' + self.cut_buffer = "" self.buffer = [] self.interp = interp self.interp.syntaxerror_callback = self.clear_current_line self.match = False - self.rl_history = History(duplicates=config.hist_duplicates, - hist_size=config.hist_length) + self.rl_history = History( + duplicates=config.hist_duplicates, hist_size=config.hist_length + ) self.s_hist = [] self.history = [] self.evaluating = False @@ -445,9 +451,9 @@ def __init__(self, interp, config): self.interact = Interaction(self.config) # previous pastebin content to prevent duplicate pastes, filled on call # to repl.pastebin - self.prev_pastebin_content = '' - self.prev_pastebin_url = '' - self.prev_removal_url = '' + self.prev_pastebin_content = "" + self.prev_pastebin_url = "" + self.prev_removal_url = "" # Necessary to fix mercurial.ui.ui expecting sys.stderr to have this # attribute self.closed = False @@ -456,20 +462,24 @@ def __init__(self, interp, config): pythonhist = os.path.expanduser(self.config.hist_file) if os.path.exists(pythonhist): try: - self.rl_history.load(pythonhist, - getpreferredencoding() or "ascii") + self.rl_history.load( + pythonhist, getpreferredencoding() or "ascii" + ) except EnvironmentError: pass self.completers = autocomplete.get_default_completer( - config.autocomplete_mode) + config.autocomplete_mode + ) if self.config.pastebin_helper: self.paster = PasteHelper(self.config.pastebin_helper) else: - self.paster = PastePinnwand(self.config.pastebin_url, - self.config.pastebin_expiry, - self.config.pastebin_show_url, - self.config.pastebin_removal_url) + self.paster = PastePinnwand( + self.config.pastebin_url, + self.config.pastebin_expiry, + self.config.pastebin_show_url, + self.config.pastebin_removal_url, + ) @property def ps1(self): @@ -479,7 +489,7 @@ def ps1(self): else: return sys.ps1 except AttributeError: - return u'>>> ' + return u">>> " @property def ps2(self): @@ -490,31 +500,33 @@ def ps2(self): return sys.ps2 except AttributeError: - return u'... ' + return u"... " def startup(self): """ Execute PYTHONSTARTUP file if it exits. Call this after front end-specific initialisation. """ - filename = os.environ.get('PYTHONSTARTUP') + filename = os.environ.get("PYTHONSTARTUP") if filename: encoding = inspection.get_encoding_file(filename) - with io.open(filename, 'rt', encoding=encoding) as f: + with io.open(filename, "rt", encoding=encoding) as f: source = f.read() if not py3: # Early Python 2.7.X need bytes. source = source.encode(encoding) - self.interp.runsource(source, filename, 'exec', encode=False) + self.interp.runsource(source, filename, "exec", encode=False) def current_string(self, concatenate=False): """If the line ends in a string get it, otherwise return ''""" tokens = self.tokenize(self.current_line) - string_tokens = list(takewhile(token_is_any_of([Token.String, - Token.Text]), - reversed(tokens))) + string_tokens = list( + takewhile( + token_is_any_of([Token.String, Token.Text]), reversed(tokens) + ) + ) if not string_tokens: - return '' + return "" opening = string_tokens.pop()[1] string = list() for (token, value) in reversed(string_tokens): @@ -533,11 +545,11 @@ def current_string(self, concatenate=False): string.append(value) if opening is None: - return '' - return ''.join(string) + return "" + return "".join(string) def get_object(self, name): - attributes = name.split('.') + attributes = name.split(".") obj = eval(attributes.pop(0), self.interp.locals) while attributes: with inspection.AttrCleaner(obj): @@ -551,50 +563,54 @@ def _funcname_and_argnum(cls, line): # [full_expr, function_expr, arg_number, opening] # arg_number may be a string if we've encountered a keyword # argument so we're done counting - stack = [['', '', 0, '']] + stack = [["", "", 0, ""]] try: for (token, value) in PythonLexer().get_tokens(line): if token is Token.Punctuation: - if value in '([{': - stack.append(['', '', 0, value]) - elif value in ')]}': + if value in "([{": + stack.append(["", "", 0, value]) + elif value in ")]}": full, _, _, start = stack.pop() expr = start + full + value stack[-1][1] += expr stack[-1][0] += expr - elif value == ',': + elif value == ",": try: stack[-1][2] += 1 except TypeError: - stack[-1][2] = '' - stack[-1][1] = '' + stack[-1][2] = "" + stack[-1][1] = "" stack[-1][0] += value - elif value == ':' and stack[-1][3] == 'lambda': - expr = stack.pop()[0] + ':' + elif value == ":" and stack[-1][3] == "lambda": + expr = stack.pop()[0] + ":" stack[-1][1] += expr stack[-1][0] += expr else: - stack[-1][1] = '' + stack[-1][1] = "" stack[-1][0] += value - elif (token is Token.Number or - token in Token.Number.subtypes or - token is Token.Name or token in Token.Name.subtypes or - token is Token.Operator and value == '.'): + elif ( + token is Token.Number + or token in Token.Number.subtypes + or token is Token.Name + or token in Token.Name.subtypes + or token is Token.Operator + and value == "." + ): stack[-1][1] += value stack[-1][0] += value - elif token is Token.Operator and value == '=': + elif token is Token.Operator and value == "=": stack[-1][2] = stack[-1][1] - stack[-1][1] = '' + stack[-1][1] = "" stack[-1][0] += value elif token is Token.Number or token in Token.Number.subtypes: stack[-1][1] = value stack[-1][0] += value - elif token is Token.Keyword and value == 'lambda': - stack.append([value, '', 0, value]) + elif token is Token.Keyword and value == "lambda": + stack.append([value, "", 0, value]) else: - stack[-1][1] = '' + stack[-1][1] = "" stack[-1][0] += value - while stack[-1][3] in '[{': + while stack[-1][3] in "[{": stack.pop() _, _, arg_number, _ = stack.pop() _, func, _, _ = stack.pop() @@ -623,22 +639,24 @@ def get_args(self): try: fake_cursor = self.current_line.index(func) + len(func) f = simpleeval.evaluate_current_attribute( - fake_cursor, self.current_line, self.interp.locals) + fake_cursor, self.current_line, self.interp.locals + ) except simpleeval.EvaluationError: return False if inspect.isclass(f): class_f = None - if (hasattr(f, '__init__') and - f.__init__ is not object.__init__): + if hasattr(f, "__init__") and f.__init__ is not object.__init__: class_f = f.__init__ - if ((not class_f or - not inspection.getfuncprops(func, class_f)) and - hasattr(f, '__new__') and - f.__new__ is not object.__new__ and - # py3 - f.__new__.__class__ is not object.__new__.__class__): + if ( + (not class_f or not inspection.getfuncprops(func, class_f)) + and hasattr(f, "__new__") + and f.__new__ is not object.__new__ + and + # py3 + f.__new__.__class__ is not object.__new__.__class__ + ): class_f = f.__new__ @@ -674,14 +692,14 @@ def get_source_of_current_name(self): obj = self.get_object(line) return inspection.get_source_unicode(obj) except (AttributeError, NameError) as e: - msg = _(u"Cannot get source: %s") % (e, ) + msg = _(u"Cannot get source: %s") % (e,) except IOError as e: - msg = u"%s" % (e, ) + msg = u"%s" % (e,) except TypeError as e: - if "built-in" in u"%s" % (e, ): - msg = _("Cannot access source of %r") % (obj, ) + if "built-in" in u"%s" % (e,): + msg = _("Cannot access source of %r") % (obj,) else: - msg = _("No source code found for %s") % (self.current_line, ) + msg = _("No source code found for %s") % (self.current_line,) raise SourceNotFound(msg) def set_docstring(self): @@ -730,23 +748,27 @@ def complete(self, tab=False): line=self.current_line, locals_=self.interp.locals, argspec=self.funcprops, - current_block='\n'.join(self.buffer + [self.current_line]), + current_block="\n".join(self.buffer + [self.current_line]), complete_magic_methods=self.config.complete_magic_methods, - history=self.history) + history=self.history, + ) if len(matches) == 0: self.matches_iter.clear() return bool(self.funcprops) - self.matches_iter.update(self.cursor_offset, - self.current_line, matches, completer) + self.matches_iter.update( + self.cursor_offset, self.current_line, matches, completer + ) if len(matches) == 1: if tab: # if this complete is being run for a tab key press, substitute # common sequence - self._cursor_offset, self._current_line = \ - self.matches_iter.substitute_cseq() + ( + self._cursor_offset, + self._current_line, + ) = self.matches_iter.substitute_cseq() return Repl.complete(self) # again for elif self.matches_iter.current_word == matches[0]: self.matches_iter.clear() @@ -760,15 +782,15 @@ def format_docstring(self, docstring, width, height): """Take a string and try to format it into a sane list of strings to be put into the suggestion box.""" - lines = docstring.split('\n') + lines = docstring.split("\n") out = [] i = 0 for line in lines: i += 1 if not line.strip(): - out.append('\n') + out.append("\n") for block in textwrap.wrap(line, width): - out.append(' ' + block + '\n') + out.append(" " + block + "\n") if i >= height: return out i += 1 @@ -780,11 +802,14 @@ def next_indentation(self): """Return the indentation of the next line based on the current input buffer.""" if self.buffer: - indentation = next_indentation(self.buffer[-1], - self.config.tab_length) + indentation = next_indentation( + self.buffer[-1], self.config.tab_length + ) if indentation and self.config.dedent_after > 0: + def line_is_empty(line): return not line.strip() + empty_lines = takewhile(line_is_empty, reversed(self.buffer)) if sum(1 for _ in empty_lines) >= self.config.dedent_after: indentation -= 1 @@ -798,13 +823,14 @@ def formatforfile(self, session_ouput): output lines.""" def process(): - for line in session_ouput.split('\n'): + for line in session_ouput.split("\n"): if line.startswith(self.ps1): - yield line[len(self.ps1):] + yield line[len(self.ps1) :] elif line.startswith(self.ps2): - yield line[len(self.ps2):] + yield line[len(self.ps2) :] elif line.rstrip(): yield "# OUT: %s" % (line,) + return "\n".join(process()) def write2file(self): @@ -812,31 +838,36 @@ def write2file(self): buffer to disk.""" try: - fn = self.interact.file_prompt(_('Save to file (Esc to cancel): ')) + fn = self.interact.file_prompt(_("Save to file (Esc to cancel): ")) if not fn: - self.interact.notify(_('Save cancelled.')) + self.interact.notify(_("Save cancelled.")) return except ValueError: - self.interact.notify(_('Save cancelled.')) + self.interact.notify(_("Save cancelled.")) return - if fn.startswith('~'): + if fn.startswith("~"): fn = os.path.expanduser(fn) - if not fn.endswith('.py') and self.config.save_append_py: - fn = fn + '.py' + if not fn.endswith(".py") and self.config.save_append_py: + fn = fn + ".py" - mode = 'w' + mode = "w" if os.path.exists(fn): - mode = self.interact.file_prompt(_('%s already exists. Do you ' - 'want to (c)ancel, ' - ' (o)verwrite or ' - '(a)ppend? ') % (fn, )) - if mode in ('o', 'overwrite', _('overwrite')): - mode = 'w' - elif mode in ('a', 'append', _('append')): - mode = 'a' + mode = self.interact.file_prompt( + _( + "%s already exists. Do you " + "want to (c)ancel, " + " (o)verwrite or " + "(a)ppend? " + ) + % (fn,) + ) + if mode in ("o", "overwrite", _("overwrite")): + mode = "w" + elif mode in ("a", "append", _("append")): + mode = "a" else: - self.interact.notify(_('Save cancelled.')) + self.interact.notify(_("Save cancelled.")) return stdout_text = self.formatforfile(self.getstdout()) @@ -847,22 +878,22 @@ def write2file(self): except IOError as e: self.interact.notify(_("Error writing file '%s': %s") % (fn, e)) else: - self.interact.notify(_('Saved to %s.') % (fn, )) + self.interact.notify(_("Saved to %s.") % (fn,)) def copy2clipboard(self): """Copy current content to clipboard.""" if self.clipboard is None: - self.interact.notify(_('No clipboard available.')) + self.interact.notify(_("No clipboard available.")) return content = self.formatforfile(self.getstdout()) try: self.clipboard.copy(content) except CopyFailed: - self.interact.notify(_('Could not copy to clipboard.')) + self.interact.notify(_("Could not copy to clipboard.")) else: - self.interact.notify(_('Copied content to clipboard.')) + self.interact.notify(_("Copied content to clipboard.")) def pastebin(self, s=None): """Upload to a pastebin and display the URL in the status bar.""" @@ -870,8 +901,9 @@ def pastebin(self, s=None): if s is None: s = self.getstdout() - if (self.config.pastebin_confirm and - not self.interact.confirm(_("Pastebin buffer? (y/N) "))): + if self.config.pastebin_confirm and not self.interact.confirm( + _("Pastebin buffer? (y/N) ") + ): self.interact.notify(_("Pastebin aborted.")) return return self.do_pastebin(s) @@ -879,17 +911,18 @@ def pastebin(self, s=None): def do_pastebin(self, s): """Actually perform the upload.""" if s == self.prev_pastebin_content: - self.interact.notify(_('Duplicate pastebin. Previous URL: %s. ' - 'Removal URL: %s') % - (self.prev_pastebin_url, - self.prev_removal_url), 10) + self.interact.notify( + _("Duplicate pastebin. Previous URL: %s. " "Removal URL: %s") + % (self.prev_pastebin_url, self.prev_removal_url), + 10, + ) return self.prev_pastebin_url - self.interact.notify(_('Posting data to pastebin...')) + self.interact.notify(_("Posting data to pastebin...")) try: paste_url, removal_url = self.paster.paste(s) except PasteFailed as e: - self.interact.notify(_('Upload failed: %s') % e) + self.interact.notify(_("Upload failed: %s") % e) return self.prev_pastebin_content = s @@ -897,23 +930,26 @@ def do_pastebin(self, s): self.prev_removal_url = removal_url if removal_url is not None: - self.interact.notify(_('Pastebin URL: %s - Removal URL: %s') % - (paste_url, removal_url), 10) + self.interact.notify( + _("Pastebin URL: %s - Removal URL: %s") + % (paste_url, removal_url), + 10, + ) else: - self.interact.notify(_('Pastebin URL: %s') % (paste_url, ), 10) + self.interact.notify(_("Pastebin URL: %s") % (paste_url,), 10) return paste_url def push(self, s, insert_into_history=True): """Push a line of code onto the buffer so it can process it all at once when a code block ends""" - s = s.rstrip('\n') + s = s.rstrip("\n") self.buffer.append(s) if insert_into_history: self.insert_into_history(s) - more = self.interp.runsource('\n'.join(self.buffer)) + more = self.interp.runsource("\n".join(self.buffer)) if not more: self.buffer = [] @@ -923,36 +959,42 @@ def push(self, s, insert_into_history=True): def insert_into_history(self, s): pythonhist = os.path.expanduser(self.config.hist_file) try: - self.rl_history.append_reload_and_write(s, pythonhist, - getpreferredencoding()) + self.rl_history.append_reload_and_write( + s, pythonhist, getpreferredencoding() + ) except RuntimeError as e: - self.interact.notify(u"%s" % (e, )) + self.interact.notify(u"%s" % (e,)) def prompt_undo(self): """Returns how many lines to undo, 0 means don't undo""" - if (self.config.single_undo_time < 0 or - self.interp.timer.estimate() < self.config.single_undo_time): + if ( + self.config.single_undo_time < 0 + or self.interp.timer.estimate() < self.config.single_undo_time + ): return 1 est = self.interp.timer.estimate() n = self.interact.file_prompt( _("Undo how many lines? (Undo will take up to ~%.1f seconds) [1]") - % (est,)) + % (est,) + ) try: - if n == '': - n = '1' + if n == "": + n = "1" n = int(n) except ValueError: - self.interact.notify(_('Undo canceled'), .1) + self.interact.notify(_("Undo canceled"), 0.1) return 0 else: if n == 0: - self.interact.notify(_('Undo canceled'), .1) + self.interact.notify(_("Undo canceled"), 0.1) return 0 else: - message = ngettext('Undoing %d line... (est. %.1f seconds)', - 'Undoing %d lines... (est. %.1f seconds)', - n) - self.interact.notify(message % (n, est), .1) + message = ngettext( + "Undoing %d line... (est. %.1f seconds)", + "Undoing %d lines... (est. %.1f seconds)", + n, + ) + self.interact.notify(message % (n, est), 0.1) return n def undo(self, n=1): @@ -1005,7 +1047,7 @@ def tokenize(self, s, newline=False): """ highlighted_paren = None - source = '\n'.join(self.buffer + [s]) + source = "\n".join(self.buffer + [s]) cursor = len(source) - self.cpos if self.cpos: cursor += 1 @@ -1015,15 +1057,15 @@ def tokenize(self, s, newline=False): # no size, so strip them while not all_tokens[-1][1]: all_tokens.pop() - all_tokens[-1] = (all_tokens[-1][0], all_tokens[-1][1].rstrip('\n')) + all_tokens[-1] = (all_tokens[-1][0], all_tokens[-1][1].rstrip("\n")) line = pos = 0 - parens = dict(zip('{([', '})]')) + parens = dict(zip("{([", "})]")) line_tokens = list() saved_tokens = list() search_for_paren = True for (token, value) in split_lines(all_tokens): pos += len(value) - if token is Token.Text and value == '\n': + if token is Token.Text and value == "\n": line += 1 # Remove trailing newline line_tokens = list() @@ -1033,7 +1075,7 @@ def tokenize(self, s, newline=False): saved_tokens.append((token, value)) if not search_for_paren: continue - under_cursor = (pos == cursor) + under_cursor = pos == cursor if token is Token.Punctuation: if value in parens: if under_cursor: @@ -1041,8 +1083,9 @@ def tokenize(self, s, newline=False): # Push marker on the stack stack.append((Parenthesis, value)) else: - stack.append((line, len(line_tokens) - 1, - line_tokens, value)) + stack.append( + (line, len(line_tokens) - 1, line_tokens, value) + ) elif value in itervalues(parens): saved_stack = list(stack) try: @@ -1098,9 +1141,10 @@ def send_to_external_editor(self, text): exited with non-zero""" encoding = getpreferredencoding() - editor_args = shlex.split(prepare_for_exec(self.config.editor, - encoding)) - with tempfile.NamedTemporaryFile(suffix='.py') as temp: + editor_args = shlex.split( + prepare_for_exec(self.config.editor, encoding) + ) + with tempfile.NamedTemporaryFile(suffix=".py") as temp: temp.write(text.encode(encoding)) temp.flush() @@ -1116,49 +1160,62 @@ def send_to_external_editor(self, text): def open_in_external_editor(self, filename): encoding = getpreferredencoding() - editor_args = shlex.split(prepare_for_exec(self.config.editor, - encoding)) + editor_args = shlex.split( + prepare_for_exec(self.config.editor, encoding) + ) args = editor_args + [prepare_for_exec(filename, encoding)] return subprocess.call(args) == 0 def edit_config(self): if not os.path.isfile(self.config.config_path): - if self.interact.confirm(_("Config file does not exist - create " - "new from default? (y/N)")): + if self.interact.confirm( + _( + "Config file does not exist - create " + "new from default? (y/N)" + ) + ): try: - default_config = pkgutil.get_data('bpython', - 'sample-config') + default_config = pkgutil.get_data( + "bpython", "sample-config" + ) if py3: # py3 files need unicode - default_config = default_config.decode('ascii') + default_config = default_config.decode("ascii") containing_dir = os.path.dirname( - os.path.abspath(self.config.config_path)) + os.path.abspath(self.config.config_path) + ) if not os.path.exists(containing_dir): os.makedirs(containing_dir) - with open(self.config.config_path, 'w') as f: + with open(self.config.config_path, "w") as f: f.write(default_config) except (IOError, OSError) as e: - self.interact.notify(_("Error writing file '%s': %s") % - (self.config.config.path, e)) + self.interact.notify( + _("Error writing file '%s': %s") + % (self.config.config.path, e) + ) return False else: return False try: if self.open_in_external_editor(self.config.config_path): - self.interact.notify(_('bpython config file edited. Restart ' - 'bpython for changes to take effect.')) + self.interact.notify( + _( + "bpython config file edited. Restart " + "bpython for changes to take effect." + ) + ) except OSError as e: - self.interact.notify(_('Error editing config file: %s') % e) + self.interact.notify(_("Error editing config file: %s") % e) def next_indentation(line, tab_length): """Given a code line, return the indentation of the next line.""" line = line.expandtabs(tab_length) - indentation = (len(line) - len(line.lstrip(' '))) // tab_length - if line.rstrip().endswith(':'): + indentation = (len(line) - len(line.lstrip(" "))) // tab_length + if line.rstrip().endswith(":"): indentation += 1 elif indentation >= 1: - if line.lstrip().startswith(('return', 'pass', 'raise', 'yield')): + if line.lstrip().startswith(("return", "pass", "raise", "yield")): indentation -= 1 return indentation @@ -1168,7 +1225,7 @@ def next_token_inside_string(code_string, inside_string): whether the next token will be inside a string or not.""" for token, value in PythonLexer().get_tokens(code_string): if token is Token.String: - value = value.lstrip('bBrRuU') + value = value.lstrip("bBrRuU") if value in ['"""', "'''", '"', "'"]: if not inside_string: inside_string = value @@ -1182,7 +1239,7 @@ def split_lines(tokens): if not value: continue while value: - head, newline, value = value.partition('\n') + head, newline, value = value.partition("\n") yield (token, head) if newline: yield (Token.Text, newline) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 986fcad31..1f87b76c3 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -43,7 +43,7 @@ _numeric_types = (int, float, complex) + (() if py3 else (long,)) # added in Python 3.4 -if hasattr(ast, 'NameConstant'): +if hasattr(ast, "NameConstant"): _name_type_nodes = (ast.Name, ast.NameConstant) else: _name_type_nodes = (ast.Name,) @@ -92,7 +92,7 @@ def simple_eval(node_or_string, namespace=None): if namespace is None: namespace = {} if isinstance(node_or_string, string_types): - node_or_string = ast.parse(node_or_string, mode='eval') + node_or_string = ast.parse(node_or_string, mode="eval") if isinstance(node_or_string, ast.Expression): node_or_string = node_or_string.body @@ -106,8 +106,10 @@ def _convert(node): elif isinstance(node, ast.List): return list(map(_convert, node.elts)) elif isinstance(node, ast.Dict): - return dict((_convert(k), _convert(v)) for k, v - in zip(node.keys, node.values)) + return dict( + (_convert(k), _convert(v)) + for k, v in zip(node.keys, node.values) + ) # this is a deviation from literal_eval: we allow non-literals elif isinstance(node, _name_type_nodes): @@ -120,23 +122,26 @@ def _convert(node): raise EvaluationError("can't lookup %s" % node.id) # unary + and - are allowed on any type - elif (isinstance(node, ast.UnaryOp) and - isinstance(node.op, (ast.UAdd, ast.USub))): + elif isinstance(node, ast.UnaryOp) and isinstance( + node.op, (ast.UAdd, ast.USub) + ): # ast.literal_eval does ast typechecks here, we use type checks operand = _convert(node.operand) if not type(operand) in _numeric_types: raise ValueError("unary + and - only allowed on builtin nums") if isinstance(node.op, ast.UAdd): - return + operand + return +operand else: - return - operand - elif (isinstance(node, ast.BinOp) and - isinstance(node.op, (ast.Add, ast.Sub))): + return -operand + elif isinstance(node, ast.BinOp) and isinstance( + node.op, (ast.Add, ast.Sub) + ): # ast.literal_eval does ast typechecks here, we use type checks left = _convert(node.left) right = _convert(node.right) - if not (type(left) in _numeric_types and - type(right) in _numeric_types): + if not ( + type(left) in _numeric_types and type(right) in _numeric_types + ): raise ValueError("binary + and - only allowed on builtin nums") if isinstance(node.op, ast.Add): return left + right @@ -144,8 +149,9 @@ def _convert(node): return left - right # this is a deviation from literal_eval: we allow indexing - elif (isinstance(node, ast.Subscript) and - isinstance(node.slice, ast.Index)): + elif isinstance(node, ast.Subscript) and isinstance( + node.slice, ast.Index + ): obj = _convert(node.value) index = _convert(node.slice.value) return safe_getitem(obj, index) @@ -156,7 +162,8 @@ def _convert(node): attr = node.attr return safe_get_attribute(obj, attr) - raise ValueError('malformed string') + raise ValueError("malformed string") + return _convert(node_or_string) @@ -166,7 +173,7 @@ def safe_getitem(obj, index): return obj[index] except (KeyError, IndexError): raise EvaluationError("can't lookup key %r on %r" % (index, obj)) - raise ValueError('unsafe to lookup on object of type %s' % (type(obj), )) + raise ValueError("unsafe to lookup on object of type %s" % (type(obj),)) def find_attribute_with_name(node, name): @@ -192,13 +199,14 @@ def evaluate_current_expression(cursor_offset, line, namespace=None): namespace = {} # in case attribute is blank, e.g. foo.| -> foo.xxx| - temp_line = line[:cursor_offset] + 'xxx' + line[cursor_offset:] + temp_line = line[:cursor_offset] + "xxx" + line[cursor_offset:] temp_cursor = cursor_offset + 3 temp_attribute = line_properties.current_expression_attribute( - temp_cursor, temp_line) + temp_cursor, temp_line + ) if temp_attribute is None: raise EvaluationError("No current attribute") - attr_before_cursor = temp_line[temp_attribute.start:temp_cursor] + attr_before_cursor = temp_line[temp_attribute.start : temp_cursor] def parse_trees(cursor_offset, line): for i in range(cursor_offset - 1, -1, -1): @@ -216,7 +224,8 @@ def parse_trees(cursor_offset, line): if largest_ast is None: raise EvaluationError( - "Corresponding ASTs to right of cursor are invalid") + "Corresponding ASTs to right of cursor are invalid" + ) try: return simple_eval(largest_ast, namespace) except ValueError: @@ -236,7 +245,8 @@ def evaluate_current_attribute(cursor_offset, line, namespace=None): return getattr(obj, attr.word) except AttributeError: raise EvaluationError( - "can't lookup attribute %s on %r" % (attr.word, obj)) + "can't lookup attribute %s on %r" % (attr.word, obj) + ) def safe_get_attribute(obj, attr): @@ -252,7 +262,7 @@ def safe_get_attribute(obj, attr): class _ClassWithSlots(object): - __slots__ = ['a'] + __slots__ = ["a"] member_descriptor = type(_ClassWithSlots.a) @@ -270,12 +280,12 @@ def safe_get_attribute_new_style(obj, attr): """ if not is_new_style(obj): raise ValueError("%r is not a new-style class or object" % obj) - to_look_through = (obj.__mro__ - if inspect.isclass(obj) - else (obj,) + type(obj).__mro__) + to_look_through = ( + obj.__mro__ if inspect.isclass(obj) else (obj,) + type(obj).__mro__ + ) for cls in to_look_through: - if hasattr(cls, '__dict__') and attr in cls.__dict__: + if hasattr(cls, "__dict__") and attr in cls.__dict__: return cls.__dict__[attr] raise AttributeError() diff --git a/bpython/simplerepl.py b/bpython/simplerepl.py index 1355f270a..068f91b8a 100644 --- a/bpython/simplerepl.py +++ b/bpython/simplerepl.py @@ -49,25 +49,25 @@ def __init__(self): def _request_refresh(self): self.requested_events.append(bpythonevents.RefreshRequestEvent()) - def _schedule_refresh(self, when='now'): - if when == 'now': + def _schedule_refresh(self, when="now"): + if when == "now": self.request_refresh() else: dt = round(when - time.time(), 1) - self.out('please refresh in {} seconds'.format(dt)) + self.out("please refresh in {} seconds".format(dt)) - def _request_reload(self, files_modified=('?',)): + def _request_reload(self, files_modified=("?",)): e = bpythonevents.ReloadEvent() e.files_modified = files_modified self.requested_events.append(e) - self.out('please hit enter to trigger a refresh') + self.out("please hit enter to trigger a refresh") def request_undo(self, n=1): self.requested_events.append(bpythonevents.UndoEvent(n=n)) def out(self, msg): - if hasattr(self, 'orig_stdout'): - self.orig_stdout.write((msg + '\n').encode('utf8')) + if hasattr(self, "orig_stdout"): + self.orig_stdout.write((msg + "\n").encode("utf8")) self.orig_stdout.flush() else: print(msg) @@ -76,27 +76,27 @@ def on_suspend(self): pass def after_suspend(self): - self.out('please hit enter to trigger a refresh') + self.out("please hit enter to trigger a refresh") def print_output(self): arr, cpos = self.paint() - arr[cpos[0]:cpos[0] + 1, cpos[1]:cpos[1] + 1] = ['~'] + arr[cpos[0] : cpos[0] + 1, cpos[1] : cpos[1] + 1] = ["~"] def print_padded(s): - return self.out(s.center(self.width + 8, 'X')) + return self.out(s.center(self.width + 8, "X")) - print_padded('') + print_padded("") print_padded(' enter -> "/", rewind -> "\\", ') print_padded(' reload -> "|", pastebin -> "$", ') print_padded(' "~" is the cursor ') - print_padded('') - self.out('X``' + ('`' * (self.width + 2)) + '``X') + print_padded("") + self.out("X``" + ("`" * (self.width + 2)) + "``X") for line in arr: - self.out('X```' + unicode(line.ljust(self.width)) + '```X') - logger.debug('line:') + self.out("X```" + unicode(line.ljust(self.width)) + "```X") + logger.debug("line:") logger.debug(repr(line)) - self.out('X``' + ('`' * (self.width + 2)) + '``X') - self.out('X' * (self.width + 8)) + self.out("X``" + ("`" * (self.width + 2)) + "``X") + self.out("X" * (self.width + 8)) return max(len(arr) - self.height, 0) def get_input(self): @@ -106,13 +106,13 @@ def get_input(self): self.process_event(self.requested_events.pop()) continue c = chars.pop(0) - if c in '/': - c = '\n' - elif c in '\\': + if c in "/": + c = "\n" + elif c in "\\": c = key_dispatch[self.config.undo_key][0] - elif c in '$': + elif c in "$": c = key_dispatch[self.config.pastebin_key][0] - elif c in '|': + elif c in "|": c = key_dispatch[self.config.reimport_key][0] self.process_event(c) @@ -129,5 +129,5 @@ def main(args=None, locals_=None, banner=None): r.get_input() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index 39b5f5cb3..7ff693c0a 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -17,10 +17,9 @@ class FixLanguageTestCase(unittest.TestCase): - @classmethod def setUpClass(cls): - init(languages=['en']) + init(languages=["en"]) class MagicIterMock(mock.MagicMock): @@ -33,7 +32,7 @@ class MagicIterMock(mock.MagicMock): def builtin_target(obj): """Returns mock target string of a builtin""" - return '%s.%s' % (builtins.__name__, obj.__name__) + return "%s.%s" % (builtins.__name__, obj.__name__) TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") diff --git a/bpython/test/fodder/original.py b/bpython/test/fodder/original.py index 1afd64129..5a8ab2eb3 100644 --- a/bpython/test/fodder/original.py +++ b/bpython/test/fodder/original.py @@ -1,6 +1,7 @@ # careful: whitespace is very important in this file # also, this code runs - so everything should be a noop + class BlankLineBetweenMethods(object): def method1(self): pass @@ -8,27 +9,29 @@ def method1(self): def method2(self): pass + def BlankLineInFunction(self): return 7 pass -#StartTest-blank_lines_in_for_loop + +# StartTest-blank_lines_in_for_loop for i in range(2): pass pass -#EndTest +# EndTest -#StartTest-blank_line_in_try_catch +# StartTest-blank_line_in_try_catch try: 1 except: 2 -#EndTest +# EndTest -#StartTest-blank_line_in_try_catch_else +# StartTest-blank_line_in_try_catch_else try: 1 @@ -37,13 +40,15 @@ def BlankLineInFunction(self): else: 3 -#EndTest +# EndTest -#StartTest-blank_trailing_line +# StartTest-blank_trailing_line def foo(): return 1 -#EndTest + +# EndTest + def tabs(): - return 1 + return 1 diff --git a/bpython/test/fodder/processed.py b/bpython/test/fodder/processed.py index 6b6331662..1656ffe93 100644 --- a/bpython/test/fodder/processed.py +++ b/bpython/test/fodder/processed.py @@ -1,48 +1,53 @@ -#careful! Whitespace is very important in this file +# careful! Whitespace is very important in this file + class BlankLineBetweenMethods(object): def method1(self): pass - + def method2(self): pass + def BlankLineInFunction(self): return 7 - + pass -#StartTest-blank_lines_in_for_loop + +# StartTest-blank_lines_in_for_loop for i in range(2): pass - + pass -#EndTest +# EndTest -#StartTest-blank_line_in_try_catch +# StartTest-blank_line_in_try_catch try: 1 - + except: 2 -#EndTest +# EndTest -#StartTest-blank_line_in_try_catch_else +# StartTest-blank_line_in_try_catch_else try: 1 - + except: 2 - + else: 3 -#EndTest +# EndTest -#StartTest-blank_trailing_line +# StartTest-blank_trailing_line def foo(): return 1 -#EndTest + +# EndTest + def tabs(): return 1 diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 04d362406..f473a172f 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -7,75 +7,89 @@ from textwrap import dedent from bpython import args -from bpython.test import (FixLanguageTestCase as TestCase, unittest) +from bpython.test import FixLanguageTestCase as TestCase, unittest try: from nose.plugins.attrib import attr except ImportError: + def attr(*args, **kwargs): def identity(func): return func + return identity -@attr(speed='slow') +@attr(speed="slow") class TestExecArgs(unittest.TestCase): def test_exec_dunder_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: - f.write(dedent("""\ + f.write( + dedent( + """\ import sys sys.stderr.write(__file__) - sys.stderr.flush()""")) + sys.stderr.flush()""" + ) + ) f.flush() p = subprocess.Popen( - [sys.executable] + - (['-W', 'ignore'] if sys.version_info[:2] == (2, 6) else []) + - ["-m", "bpython.curtsies", f.name], + [sys.executable] + + (["-W", "ignore"] if sys.version_info[:2] == (2, 6) else []) + + ["-m", "bpython.curtsies", f.name], stderr=subprocess.PIPE, - universal_newlines=True) + universal_newlines=True, + ) (_, stderr) = p.communicate() self.assertEqual(stderr.strip(), f.name) def test_exec_nonascii_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: - f.write(dedent('''\ + f.write( + dedent( + """\ #!/usr/bin/env python # coding: utf-8 "你好 # nonascii" - ''')) + """ + ) + ) f.flush() try: - subprocess.check_call([ - sys.executable, '-m', 'bpython.curtsies', - f.name]) + subprocess.check_call( + [sys.executable, "-m", "bpython.curtsies", f.name] + ) except subprocess.CalledProcessError: - self.fail('Error running module with nonascii characters') + self.fail("Error running module with nonascii characters") def test_exec_nonascii_file_linenums(self): with tempfile.NamedTemporaryFile(mode="w") as f: - f.write(dedent("""\ + f.write( + dedent( + """\ #!/usr/bin/env python # coding: utf-8 1/0 - """)) + """ + ) + ) f.flush() p = subprocess.Popen( - [sys.executable, "-m", "bpython.curtsies", - f.name], + [sys.executable, "-m", "bpython.curtsies", f.name], stderr=subprocess.PIPE, - universal_newlines=True) + universal_newlines=True, + ) (_, stderr) = p.communicate() - self.assertIn('line 3', clean_colors(stderr)) + self.assertIn("line 3", clean_colors(stderr)) def clean_colors(s): - return re.sub(r'\x1b[^m]*m', '', s) + return re.sub(r"\x1b[^m]*m", "", s) class TestParse(TestCase): - def test_version(self): with self.assertRaises(SystemExit): - args.parse(['--version']) + args.parse(["--version"]) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 16c633558..535f55cb7 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -12,6 +12,7 @@ try: import jedi + has_jedi = True except ImportError: has_jedi = False @@ -22,31 +23,30 @@ is_py34 = sys.version_info[:2] >= (3, 4) if is_py34: - glob_function = 'glob.iglob' + glob_function = "glob.iglob" else: - glob_function = 'glob.glob' + glob_function = "glob.glob" class TestSafeEval(unittest.TestCase): def test_catches_syntax_error(self): with self.assertRaises(autocomplete.EvaluationError): - autocomplete.safe_eval('1re', {}) + autocomplete.safe_eval("1re", {}) class TestFormatters(unittest.TestCase): - def test_filename(self): completer = autocomplete.FilenameCompletion() last_part_of_filename = completer.format - self.assertEqual(last_part_of_filename('abc'), 'abc') - self.assertEqual(last_part_of_filename('abc/'), 'abc/') - self.assertEqual(last_part_of_filename('abc/efg'), 'efg') - self.assertEqual(last_part_of_filename('abc/efg/'), 'efg/') - self.assertEqual(last_part_of_filename('/abc'), 'abc') - self.assertEqual(last_part_of_filename('ab.c/e.f.g/'), 'e.f.g/') + self.assertEqual(last_part_of_filename("abc"), "abc") + self.assertEqual(last_part_of_filename("abc/"), "abc/") + self.assertEqual(last_part_of_filename("abc/efg"), "efg") + self.assertEqual(last_part_of_filename("abc/efg/"), "efg/") + self.assertEqual(last_part_of_filename("/abc"), "abc") + self.assertEqual(last_part_of_filename("ab.c/e.f.g/"), "e.f.g/") def test_attribute(self): - self.assertEqual(autocomplete.after_last_dot('abc.edf'), 'edf') + self.assertEqual(autocomplete.after_last_dot("abc.edf"), "edf") def completer(matches): @@ -56,44 +56,45 @@ def completer(matches): class TestGetCompleter(unittest.TestCase): - def test_no_completers(self): - self.assertTupleEqual(autocomplete.get_completer([], 0, ''), - ([], None)) + self.assertTupleEqual(autocomplete.get_completer([], 0, ""), ([], None)) def test_one_completer_without_matches_returns_empty_list_and_none(self): a = completer([]) - self.assertTupleEqual(autocomplete.get_completer([a], 0, ''), - ([], None)) + self.assertTupleEqual( + autocomplete.get_completer([a], 0, ""), ([], None) + ) def test_one_completer_returns_matches_and_completer(self): - a = completer(['a']) - self.assertTupleEqual(autocomplete.get_completer([a], 0, ''), - (['a'], a)) + a = completer(["a"]) + self.assertTupleEqual( + autocomplete.get_completer([a], 0, ""), (["a"], a) + ) def test_two_completers_with_matches_returns_first_matches(self): - a = completer(['a']) - b = completer(['b']) - self.assertEqual(autocomplete.get_completer([a, b], 0, ''), (['a'], a)) + a = completer(["a"]) + b = completer(["b"]) + self.assertEqual(autocomplete.get_completer([a, b], 0, ""), (["a"], a)) def test_first_non_none_completer_matches_are_returned(self): a = completer([]) - b = completer(['a']) - self.assertEqual(autocomplete.get_completer([a, b], 0, ''), ([], None)) + b = completer(["a"]) + self.assertEqual(autocomplete.get_completer([a, b], 0, ""), ([], None)) def test_only_completer_returns_None(self): a = completer(None) - self.assertEqual(autocomplete.get_completer([a], 0, ''), ([], None)) + self.assertEqual(autocomplete.get_completer([a], 0, ""), ([], None)) def test_first_completer_returns_None(self): a = completer(None) - b = completer(['a']) - self.assertEqual(autocomplete.get_completer([a, b], 0, ''), (['a'], b)) + b = completer(["a"]) + self.assertEqual(autocomplete.get_completer([a, b], 0, ""), (["a"], b)) class TestCumulativeCompleter(unittest.TestCase): - - def completer(self, matches, ): + def completer( + self, matches, + ): mock_completer = autocomplete.BaseCompletionType() mock_completer.matches = mock.Mock(return_value=matches) return mock_completer @@ -105,22 +106,21 @@ def test_no_completers_fails(self): def test_one_empty_completer_returns_empty(self): a = self.completer([]) cumulative = autocomplete.CumulativeCompleter([a]) - self.assertEqual(cumulative.matches(3, 'abc'), set()) + self.assertEqual(cumulative.matches(3, "abc"), set()) def test_one_none_completer_returns_none(self): a = self.completer(None) cumulative = autocomplete.CumulativeCompleter([a]) - self.assertEqual(cumulative.matches(3, 'abc'), None) + self.assertEqual(cumulative.matches(3, "abc"), None) def test_two_completers_get_both(self): - a = self.completer(['a']) - b = self.completer(['b']) + a = self.completer(["a"]) + b = self.completer(["b"]) cumulative = autocomplete.CumulativeCompleter([a, b]) - self.assertEqual(cumulative.matches(3, 'abc'), set(['a', 'b'])) + self.assertEqual(cumulative.matches(3, "abc"), set(["a", "b"])) class TestFilenameCompletion(unittest.TestCase): - def setUp(self): self.completer = autocomplete.FilenameCompletion() @@ -128,49 +128,54 @@ def test_locate_fails_when_not_in_string(self): self.assertEqual(self.completer.locate(4, "abcd"), None) def test_locate_succeeds_when_in_string(self): - self.assertEqual(self.completer.locate(4, "a'bc'd"), (2, 4, 'bc')) + self.assertEqual(self.completer.locate(4, "a'bc'd"), (2, 4, "bc")) def test_issue_491(self): self.assertNotEqual(self.completer.matches(9, '"a[a.l-1]'), None) @mock.patch(glob_function, new=lambda text: []) def test_match_returns_none_if_not_in_string(self): - self.assertEqual(self.completer.matches(2, 'abcd'), None) + self.assertEqual(self.completer.matches(2, "abcd"), None) @mock.patch(glob_function, new=lambda text: []) def test_match_returns_empty_list_when_no_files(self): self.assertEqual(self.completer.matches(2, '"a'), set()) - @mock.patch(glob_function, new=lambda text: ['abcde', 'aaaaa']) - @mock.patch('os.path.expanduser', new=lambda text: text) - @mock.patch('os.path.isdir', new=lambda text: False) - @mock.patch('os.path.sep', new='/') + @mock.patch(glob_function, new=lambda text: ["abcde", "aaaaa"]) + @mock.patch("os.path.expanduser", new=lambda text: text) + @mock.patch("os.path.isdir", new=lambda text: False) + @mock.patch("os.path.sep", new="/") def test_match_returns_files_when_files_exist(self): - self.assertEqual(sorted(self.completer.matches(2, '"x')), - ['aaaaa', 'abcde']) - - @mock.patch(glob_function, new=lambda text: ['abcde', 'aaaaa']) - @mock.patch('os.path.expanduser', new=lambda text: text) - @mock.patch('os.path.isdir', new=lambda text: True) - @mock.patch('os.path.sep', new='/') + self.assertEqual( + sorted(self.completer.matches(2, '"x')), ["aaaaa", "abcde"] + ) + + @mock.patch(glob_function, new=lambda text: ["abcde", "aaaaa"]) + @mock.patch("os.path.expanduser", new=lambda text: text) + @mock.patch("os.path.isdir", new=lambda text: True) + @mock.patch("os.path.sep", new="/") def test_match_returns_dirs_when_dirs_exist(self): - self.assertEqual(sorted(self.completer.matches(2, '"x')), - ['aaaaa/', 'abcde/']) - - @mock.patch(glob_function, - new=lambda text: ['/expand/ed/abcde', '/expand/ed/aaaaa']) - @mock.patch('os.path.expanduser', - new=lambda text: text.replace('~', '/expand/ed')) - @mock.patch('os.path.isdir', new=lambda text: False) - @mock.patch('os.path.sep', new='/') + self.assertEqual( + sorted(self.completer.matches(2, '"x')), ["aaaaa/", "abcde/"] + ) + + @mock.patch( + glob_function, new=lambda text: ["/expand/ed/abcde", "/expand/ed/aaaaa"] + ) + @mock.patch( + "os.path.expanduser", new=lambda text: text.replace("~", "/expand/ed") + ) + @mock.patch("os.path.isdir", new=lambda text: False) + @mock.patch("os.path.sep", new="/") def test_tilde_stays_pretty(self): - self.assertEqual(sorted(self.completer.matches(4, '"~/a')), - ['~/aaaaa', '~/abcde']) + self.assertEqual( + sorted(self.completer.matches(4, '"~/a')), ["~/aaaaa", "~/abcde"] + ) - @mock.patch('os.path.sep', new='/') + @mock.patch("os.path.sep", new="/") def test_formatting_takes_just_last_part(self): - self.assertEqual(self.completer.format('/hello/there/'), 'there/') - self.assertEqual(self.completer.format('/hello/there'), 'there') + self.assertEqual(self.completer.format("/hello/there/"), "there/") + self.assertEqual(self.completer.format("/hello/there"), "there") class MockNumPy(object): @@ -178,36 +183,38 @@ class MockNumPy(object): to convert it to a boolean.""" def __nonzero__(self): - raise ValueError("The truth value of an array with more than one " - "element is ambiguous. Use a.any() or a.all()") + raise ValueError( + "The truth value of an array with more than one " + "element is ambiguous. Use a.any() or a.all()" + ) class TestDictKeyCompletion(unittest.TestCase): - def test_set_of_keys_returned_when_matches_found(self): com = autocomplete.DictKeyCompletion() - local = {'d': {"ab": 1, "cd": 2}} - self.assertSetEqual(com.matches(2, "d[", locals_=local), - set(["'ab']", "'cd']"])) + local = {"d": {"ab": 1, "cd": 2}} + self.assertSetEqual( + com.matches(2, "d[", locals_=local), set(["'ab']", "'cd']"]) + ) def test_none_returned_when_eval_error(self): com = autocomplete.DictKeyCompletion() - local = {'e': {"ab": 1, "cd": 2}} + local = {"e": {"ab": 1, "cd": 2}} self.assertEqual(com.matches(2, "d[", locals_=local), None) def test_none_returned_when_not_dict_type(self): com = autocomplete.DictKeyCompletion() - local = {'l': ["ab", "cd"]} + local = {"l": ["ab", "cd"]} self.assertEqual(com.matches(2, "l[", locals_=local), None) def test_none_returned_when_no_matches_left(self): com = autocomplete.DictKeyCompletion() - local = {'d': {"ab": 1, "cd": 2}} + local = {"d": {"ab": 1, "cd": 2}} self.assertEqual(com.matches(3, "d[r", locals_=local), None) def test_obj_that_does_not_allow_conversion_to_bool(self): com = autocomplete.DictKeyCompletion() - local = {'mNumPy': MockNumPy()} + local = {"mNumPy": MockNumPy()} self.assertEqual(com.matches(7, "mNumPy[", locals_=local), None) @@ -231,19 +238,19 @@ def method(self, x): pass -skip_old_style = unittest.skipIf(py3, - 'In Python 3 there are no old style classes') +skip_old_style = unittest.skipIf( + py3, "In Python 3 there are no old style classes" +) class Properties(Foo): - @property def asserts_when_called(self): raise AssertionError("getter method called") class Slots(object): - __slots__ = ['a', 'b'] + __slots__ = ["a", "b"] class TestAttrCompletion(unittest.TestCase): @@ -252,22 +259,28 @@ def setUpClass(cls): cls.com = autocomplete.AttrCompletion() def test_att_matches_found_on_instance(self): - self.assertSetEqual(self.com.matches(2, 'a.', locals_={'a': Foo()}), - set(['a.method', 'a.a', 'a.b'])) + self.assertSetEqual( + self.com.matches(2, "a.", locals_={"a": Foo()}), + set(["a.method", "a.a", "a.b"]), + ) @skip_old_style def test_att_matches_found_on_old_style_instance(self): - self.assertSetEqual(self.com.matches(2, 'a.', - locals_={'a': OldStyleFoo()}), - set(['a.method', 'a.a', 'a.b'])) - self.assertIn(u'a.__dict__', - self.com.matches(4, 'a.__', - locals_={'a': OldStyleFoo()})) + self.assertSetEqual( + self.com.matches(2, "a.", locals_={"a": OldStyleFoo()}), + set(["a.method", "a.a", "a.b"]), + ) + self.assertIn( + u"a.__dict__", + self.com.matches(4, "a.__", locals_={"a": OldStyleFoo()}), + ) @skip_old_style def test_att_matches_found_on_old_style_class_object(self): - self.assertIn(u'A.__dict__', - self.com.matches(4, 'A.__', locals_={'A': OldStyleFoo})) + self.assertIn( + u"A.__dict__", + self.com.matches(4, "A.__", locals_={"A": OldStyleFoo}), + ) @skip_old_style def test_issue536(self): @@ -275,20 +288,24 @@ class OldStyleWithBrokenGetAttr: def __getattr__(self, attr): raise Exception() - locals_ = {'a': OldStyleWithBrokenGetAttr()} - self.assertIn(u'a.__module__', - self.com.matches(4, 'a.__', locals_=locals_)) + locals_ = {"a": OldStyleWithBrokenGetAttr()} + self.assertIn( + u"a.__module__", self.com.matches(4, "a.__", locals_=locals_) + ) def test_descriptor_attributes_not_run(self): com = autocomplete.AttrCompletion() - self.assertSetEqual(com.matches(2, 'a.', locals_={'a': Properties()}), - set(['a.b', 'a.a', 'a.method', - 'a.asserts_when_called'])) + self.assertSetEqual( + com.matches(2, "a.", locals_={"a": Properties()}), + set(["a.b", "a.a", "a.method", "a.asserts_when_called"]), + ) def test_slots_not_crash(self): com = autocomplete.AttrCompletion() - self.assertSetEqual(com.matches(2, 'A.', locals_={'A': Slots}), - set(['A.b', 'A.a', 'A.mro'])) + self.assertSetEqual( + com.matches(2, "A.", locals_={"A": Slots}), + set(["A.b", "A.a", "A.mro"]), + ) class TestExpressionAttributeCompletion(unittest.TestCase): @@ -297,110 +314,131 @@ def setUpClass(cls): cls.com = autocomplete.ExpressionAttributeCompletion() def test_att_matches_found_on_instance(self): - self.assertSetEqual(self.com.matches(5, 'a[0].', - locals_={'a': [Foo()]}), - set(['method', 'a', 'b'])) + self.assertSetEqual( + self.com.matches(5, "a[0].", locals_={"a": [Foo()]}), + set(["method", "a", "b"]), + ) @skip_old_style def test_att_matches_found_on_old_style_instance(self): - self.assertSetEqual(self.com.matches(5, 'a[0].', - locals_={'a': [OldStyleFoo()]}), - set(['method', 'a', 'b'])) + self.assertSetEqual( + self.com.matches(5, "a[0].", locals_={"a": [OldStyleFoo()]}), + set(["method", "a", "b"]), + ) def test_other_getitem_methods_not_called(self): class FakeList(object): def __getitem__(inner_self, i): self.fail("possibly side-effecting __getitem_ method called") - self.com.matches(5, 'a[0].', locals_={'a': FakeList()}) + self.com.matches(5, "a[0].", locals_={"a": FakeList()}) def test_tuples_complete(self): - self.assertSetEqual(self.com.matches(5, 'a[0].', - locals_={'a': (Foo(),)}), - set(['method', 'a', 'b'])) + self.assertSetEqual( + self.com.matches(5, "a[0].", locals_={"a": (Foo(),)}), + set(["method", "a", "b"]), + ) - @unittest.skip('TODO, subclasses do not complete yet') + @unittest.skip("TODO, subclasses do not complete yet") def test_list_subclasses_complete(self): class ListSubclass(list): pass - self.assertSetEqual(self.com.matches(5, 'a[0].', - locals_={'a': ListSubclass([Foo()])}), - set(['method', 'a', 'b'])) + + self.assertSetEqual( + self.com.matches(5, "a[0].", locals_={"a": ListSubclass([Foo()])}), + set(["method", "a", "b"]), + ) def test_getitem_not_called_in_list_subclasses_overriding_getitem(self): class FakeList(list): def __getitem__(inner_self, i): self.fail("possibly side-effecting __getitem_ method called") - self.com.matches(5, 'a[0].', locals_={'a': FakeList()}) + self.com.matches(5, "a[0].", locals_={"a": FakeList()}) def test_literals_complete(self): - self.assertSetEqual(self.com.matches(10, '[a][0][0].', - locals_={'a': (Foo(),)}), - set(['method', 'a', 'b'])) + self.assertSetEqual( + self.com.matches(10, "[a][0][0].", locals_={"a": (Foo(),)}), + set(["method", "a", "b"]), + ) def test_dictionaries_complete(self): - self.assertSetEqual(self.com.matches(7, 'a["b"].', - locals_={'a': {'b': Foo()}}), - set(['method', 'a', 'b'])) + self.assertSetEqual( + self.com.matches(7, 'a["b"].', locals_={"a": {"b": Foo()}}), + set(["method", "a", "b"]), + ) class TestMagicMethodCompletion(unittest.TestCase): - def test_magic_methods_complete_after_double_underscores(self): com = autocomplete.MagicMethodCompletion() block = "class Something(object)\n def __" - self.assertSetEqual(com.matches(10, ' def __', current_block=block), - set(autocomplete.MAGIC_METHODS)) + self.assertSetEqual( + com.matches(10, " def __", current_block=block), + set(autocomplete.MAGIC_METHODS), + ) -Comp = namedtuple('Completion', ['name', 'complete']) +Comp = namedtuple("Completion", ["name", "complete"]) @unittest.skipUnless(has_jedi, "jedi required") class TestMultilineJediCompletion(unittest.TestCase): - def test_returns_none_with_single_line(self): com = autocomplete.MultilineJediCompletion() - self.assertEqual(com.matches(2, 'Va', current_block='Va', history=[]), - None) + self.assertEqual( + com.matches(2, "Va", current_block="Va", history=[]), None + ) def test_returns_non_with_blank_second_line(self): com = autocomplete.MultilineJediCompletion() - self.assertEqual(com.matches(0, '', current_block='class Foo():\n', - history=['class Foo():']), None) - - def matches_from_completions(self, cursor, line, block, history, - completions): - with mock.patch('bpython.autocomplete.jedi.Script') as Script: + self.assertEqual( + com.matches( + 0, "", current_block="class Foo():\n", history=["class Foo():"] + ), + None, + ) + + def matches_from_completions( + self, cursor, line, block, history, completions + ): + with mock.patch("bpython.autocomplete.jedi.Script") as Script: script = Script.return_value script.completions.return_value = completions com = autocomplete.MultilineJediCompletion() - return com.matches(cursor, line, current_block=block, - history=history) + return com.matches( + cursor, line, current_block=block, history=history + ) def test_completions_starting_with_different_letters(self): matches = self.matches_from_completions( - 2, ' a', 'class Foo:\n a', ['adsf'], - [Comp('Abc', 'bc'), Comp('Cbc', 'bc')]) + 2, + " a", + "class Foo:\n a", + ["adsf"], + [Comp("Abc", "bc"), Comp("Cbc", "bc")], + ) self.assertEqual(matches, None) def test_completions_starting_with_different_cases(self): matches = self.matches_from_completions( - 2, ' a', 'class Foo:\n a', ['adsf'], - [Comp('Abc', 'bc'), Comp('ade', 'de')]) - self.assertSetEqual(matches, set(['ade'])) - - @unittest.skipUnless(is_py34, 'asyncio required') + 2, + " a", + "class Foo:\n a", + ["adsf"], + [Comp("Abc", "bc"), Comp("ade", "de")], + ) + self.assertSetEqual(matches, set(["ade"])) + + @unittest.skipUnless(is_py34, "asyncio required") def test_issue_544(self): com = autocomplete.MultilineJediCompletion() - code = '@asyncio.coroutine\ndef' - history = ('import asyncio', '@asyncio.coroutin') - com.matches(3, 'def', current_block=code, history=history) + code = "@asyncio.coroutine\ndef" + history = ("import asyncio", "@asyncio.coroutin") + com.matches(3, "def", current_block=code, history=history) class TestGlobalCompletion(unittest.TestCase): - def setUp(self): self.com = autocomplete.GlobalCompletion() @@ -408,32 +446,33 @@ def test_function(self): def function(): pass - self.assertEqual(self.com.matches(8, 'function', - locals_={'function': function}), - set(('function(', ))) + self.assertEqual( + self.com.matches(8, "function", locals_={"function": function}), + set(("function(",)), + ) def test_completions_are_unicode(self): - for m in self.com.matches(1, 'a', locals_={'abc': 10}): - self.assertIsInstance(m, type(u'')) + for m in self.com.matches(1, "a", locals_={"abc": 10}): + self.assertIsInstance(m, type(u"")) @unittest.skipIf(py3, "in Python 3 invalid identifiers are passed through") def test_ignores_nonascii_encodable(self): - self.assertEqual(self.com.matches(3, 'abc', locals_={'abcß': 10}), - None) + self.assertEqual(self.com.matches(3, "abc", locals_={"abcß": 10}), None) def test_mock_kwlist(self): - with mock.patch.object(keyword, 'kwlist', new=['abcd']): - self.assertEqual(self.com.matches(3, 'abc', locals_={}), None) + with mock.patch.object(keyword, "kwlist", new=["abcd"]): + self.assertEqual(self.com.matches(3, "abc", locals_={}), None) def test_mock_kwlist_non_ascii(self): - with mock.patch.object(keyword, 'kwlist', new=['abcß']): - self.assertEqual(self.com.matches(3, 'abc', locals_={}), None) + with mock.patch.object(keyword, "kwlist", new=["abcß"]): + self.assertEqual(self.com.matches(3, "abc", locals_={}), None) class TestParameterNameCompletion(unittest.TestCase): def test_set_of_params_returns_when_matches_found(self): def func(apple, apricot, banana, carrot): pass + if py3: argspec = list(inspect.getfullargspec(func)) else: @@ -441,9 +480,12 @@ def func(apple, apricot, banana, carrot): argspec = ["func", argspec, False] com = autocomplete.ParameterNameCompletion() - self.assertSetEqual(com.matches(1, "a", argspec=argspec), - set(['apple=', 'apricot='])) - self.assertSetEqual(com.matches(2, "ba", argspec=argspec), - set(['banana='])) - self.assertSetEqual(com.matches(3, "car", argspec=argspec), - set(['carrot='])) + self.assertSetEqual( + com.matches(1, "a", argspec=argspec), set(["apple=", "apricot="]) + ) + self.assertSetEqual( + com.matches(2, "ba", argspec=argspec), set(["banana="]) + ) + self.assertSetEqual( + com.matches(3, "car", argspec=argspec), set(["carrot="]) + ) diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index c4c3c8b83..e8307ea1a 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -18,7 +18,7 @@ def load_temp_config(self, content, struct=None): struct = config.Struct() with tempfile.NamedTemporaryFile() as f: - f.write(content.encode('utf8')) + f.write(content.encode("utf8")) f.flush() config.loadini(struct, f.name) @@ -34,59 +34,81 @@ def test_load_theme(self): defaults = {"name": "c"} expected.update(defaults) - config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, - defaults) + config.load_theme( + struct, TEST_THEME_PATH, struct.color_scheme, defaults + ) self.assertEqual(struct.color_scheme, expected) def test_keybindings_default_contains_no_duplicates(self): struct = self.load_temp_config("") - keys = (attr for attr in dir(struct) if attr.endswith('_key')) - mapped_keys = [getattr(struct, key) for key in keys if - getattr(struct, key)] + keys = (attr for attr in dir(struct) if attr.endswith("_key")) + mapped_keys = [ + getattr(struct, key) for key in keys if getattr(struct, key) + ] mapped_keys_set = set(mapped_keys) self.assertEqual(len(mapped_keys), len(mapped_keys_set)) def test_keybindings_use_default(self): - struct = self.load_temp_config(textwrap.dedent(""" + struct = self.load_temp_config( + textwrap.dedent( + """ [keyboard] help = F1 - """)) + """ + ) + ) - self.assertEqual(struct.help_key, 'F1') + self.assertEqual(struct.help_key, "F1") def test_keybindings_use_other_default(self): - struct = self.load_temp_config(textwrap.dedent(""" + struct = self.load_temp_config( + textwrap.dedent( + """ [keyboard] help = C-h - """)) + """ + ) + ) - self.assertEqual(struct.help_key, 'C-h') - self.assertEqual(struct.backspace_key, '') + self.assertEqual(struct.help_key, "C-h") + self.assertEqual(struct.backspace_key, "") def test_keybindings_use_other_default_issue_447(self): - struct = self.load_temp_config(textwrap.dedent(""" + struct = self.load_temp_config( + textwrap.dedent( + """ [keyboard] help = F2 show_source = F9 - """)) + """ + ) + ) - self.assertEqual(struct.help_key, 'F2') - self.assertEqual(struct.show_source_key, 'F9') + self.assertEqual(struct.help_key, "F2") + self.assertEqual(struct.show_source_key, "F9") def test_keybindings_unset(self): - struct = self.load_temp_config(textwrap.dedent(""" + struct = self.load_temp_config( + textwrap.dedent( + """ [keyboard] help = - """)) + """ + ) + ) self.assertFalse(struct.help_key) def test_keybindings_unused(self): - struct = self.load_temp_config(textwrap.dedent(""" + struct = self.load_temp_config( + textwrap.dedent( + """ [keyboard] help = F4 - """)) + """ + ) + ) - self.assertEqual(struct.help_key, 'F4') + self.assertEqual(struct.help_key, "F4") diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 2e1ff4fda..d2d5c0f14 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -16,12 +16,15 @@ from twisted.internet.protocol import ProcessProtocol from twisted.trial.unittest import TestCase as TrialTestCase except ImportError: + class TrialTestCase(object): pass + reactor = None try: import urwid + have_urwid = True except ImportError: have_urwid = False @@ -29,14 +32,16 @@ class TrialTestCase(object): try: from nose.plugins.attrib import attr except ImportError: + def attr(*args, **kwargs): def identity(func): return func + return identity def set_win_size(fd, rows, columns): - s = struct.pack('HHHH', rows, columns, 0, 0) + s = struct.pack("HHHH", rows, columns, 0, 0) fcntl.ioctl(fd, termios.TIOCSWINSZ, s) @@ -73,7 +78,7 @@ def next(self): if self.state == self.SEND_INPUT: index = self.data.find(">>> ") if index >= 0: - self.data = self.data[index + 4:] + self.data = self.data[index + 4 :] self.transport.write(input) self.state = next(self.states) else: @@ -90,32 +95,41 @@ def processExited(self, reason): (master, slave) = pty.openpty() set_win_size(slave, 25, 80) reactor.spawnProcess( - Protocol(), sys.executable, - (sys.executable, "-m", "bpython." + self.backend, "--config", - TEST_CONFIG), + Protocol(), + sys.executable, + ( + sys.executable, + "-m", + "bpython." + self.backend, + "--config", + TEST_CONFIG, + ), env=dict(TERM="vt100", LANG=os.environ.get("LANG", "")), - usePTY=(master, slave, os.ttyname(slave))) + usePTY=(master, slave, os.ttyname(slave)), + ) return result - @attr(speed='slow') + @attr(speed="slow") def test_issue108(self): input = textwrap.dedent( """\ def spam(): u"y\\xe4y" \b - spam(""") + spam(""" + ) deferred = self.run_bpython(input) return deferred.addCallback(self.check_no_traceback) - @attr(speed='slow') + @attr(speed="slow") def test_issue133(self): input = textwrap.dedent( """\ def spam(a, (b, c)): pass \b - spam(1""") + spam(1""" + ) return self.run_bpython(input).addCallback(self.check_no_traceback) def check_no_traceback(self, data): diff --git a/bpython/test/test_curtsies.py b/bpython/test/test_curtsies.py index 4ea41a7e0..b337350d1 100644 --- a/bpython/test/test_curtsies.py +++ b/bpython/test/test_curtsies.py @@ -4,12 +4,12 @@ from collections import namedtuple from bpython.curtsies import combined_events -from bpython.test import (FixLanguageTestCase as TestCase, unittest) +from bpython.test import FixLanguageTestCase as TestCase, unittest import curtsies.events -ScheduledEvent = namedtuple('ScheduledEvent', ['when', 'event']) +ScheduledEvent = namedtuple("ScheduledEvent", ["when", "event"]) class EventGenerator(object): @@ -27,7 +27,7 @@ def schedule_event(self, event, when): def send(self, timeout=None): if timeout not in [None, 0]: - raise ValueError('timeout value %r not supported' % timeout) + raise ValueError("timeout value %r not supported" % timeout) if not self._events: return None if self._events[0].when <= self._current_tick: @@ -40,7 +40,7 @@ def send(self, timeout=None): self._current_tick = e.when return e.event else: - raise ValueError('timeout value %r not supported' % timeout) + raise ValueError("timeout value %r not supported" % timeout) def tick(self, dt=1): self._current_tick += dt @@ -49,43 +49,43 @@ def tick(self, dt=1): class TestCurtsiesPasteDetection(TestCase): def test_paste_threshold(self): - eg = EventGenerator(list('abc')) + eg = EventGenerator(list("abc")) cb = combined_events(eg, paste_threshold=3) e = next(cb) self.assertIsInstance(e, curtsies.events.PasteEvent) - self.assertEqual(e.events, list('abc')) + self.assertEqual(e.events, list("abc")) self.assertEqual(next(cb), None) - eg = EventGenerator(list('abc')) + eg = EventGenerator(list("abc")) cb = combined_events(eg, paste_threshold=4) - self.assertEqual(next(cb), 'a') - self.assertEqual(next(cb), 'b') - self.assertEqual(next(cb), 'c') + self.assertEqual(next(cb), "a") + self.assertEqual(next(cb), "b") + self.assertEqual(next(cb), "c") self.assertEqual(next(cb), None) def test_set_timeout(self): - eg = EventGenerator('a', zip('bcdefg', [1, 2, 3, 3, 3, 4])) + eg = EventGenerator("a", zip("bcdefg", [1, 2, 3, 3, 3, 4])) eg.schedule_event(curtsies.events.SigIntEvent(), 5) - eg.schedule_event('h', 6) + eg.schedule_event("h", 6) cb = combined_events(eg, paste_threshold=3) - self.assertEqual(next(cb), 'a') + self.assertEqual(next(cb), "a") self.assertEqual(cb.send(0), None) - self.assertEqual(next(cb), 'b') + self.assertEqual(next(cb), "b") self.assertEqual(cb.send(0), None) eg.tick() - self.assertEqual(cb.send(0), 'c') + self.assertEqual(cb.send(0), "c") self.assertEqual(cb.send(0), None) eg.tick() self.assertIsInstance(cb.send(0), curtsies.events.PasteEvent) self.assertEqual(cb.send(0), None) - self.assertEqual(cb.send(None), 'g') + self.assertEqual(cb.send(None), "g") self.assertEqual(cb.send(0), None) eg.tick(1) self.assertIsInstance(cb.send(0), curtsies.events.SigIntEvent) self.assertEqual(cb.send(0), None) - self.assertEqual(cb.send(None), 'h') + self.assertEqual(cb.send(None), "h") self.assertEqual(cb.send(None), None) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index b58806509..25844af59 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -7,7 +7,6 @@ class TestCodeRunner(unittest.TestCase): - def setUp(self): self.orig_stdout = sys.stdout self.orig_stderr = sys.stderr @@ -17,20 +16,24 @@ def tearDown(self): sys.stderr = self.orig_stderr def test_simple(self): - c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or - self.orig_stderr.flush()) + c = CodeRunner( + request_refresh=lambda: self.orig_stdout.flush() + or self.orig_stderr.flush() + ) stdout = FakeOutput(c, lambda *args, **kwargs: None) stderr = FakeOutput(c, lambda *args, **kwargs: None) sys.stdout = stdout sys.stdout = stderr - c.load_code('1 + 1') + c.load_code("1 + 1") c.run_code() c.run_code() c.run_code() def test_exception(self): - c = CodeRunner(request_refresh=lambda: self.orig_stdout.flush() or - self.orig_stderr.flush()) + c = CodeRunner( + request_refresh=lambda: self.orig_stdout.flush() + or self.orig_stderr.flush() + ) def ctrlc(): raise KeyboardInterrupt() @@ -39,15 +42,14 @@ def ctrlc(): stderr = FakeOutput(c, lambda *args, **kwargs: None) sys.stdout = stdout sys.stderr = stderr - c.load_code('1 + 1') + c.load_code("1 + 1") c.run_code() class TestFakeOutput(unittest.TestCase): - def assert_unicode(self, s): - self.assertIsInstance(s, type(u'')) + self.assertIsInstance(s, type(u"")) def test_bytes(self): out = FakeOutput(mock.Mock(), self.assert_unicode) - out.write('native string type') + out.write("native string type") diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 422d14cce..2c68b6092 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -16,8 +16,10 @@ from bpython import config, inspection from bpython.curtsiesfrontend.repl import BaseRepl from bpython.curtsiesfrontend import replpainter -from bpython.curtsiesfrontend.repl import INCONSISTENT_HISTORY_MSG, \ - CONTIGUITY_BROKEN_MSG +from bpython.curtsiesfrontend.repl import ( + INCONSISTENT_HISTORY_MSG, + CONTIGUITY_BROKEN_MSG, +) from bpython.test import FixLanguageTestCase as TestCase, TEST_CONFIG @@ -29,10 +31,9 @@ def setup_config(): class ClearEnviron(TestCase): - @classmethod def setUpClass(cls): - cls.mock_environ = mock.patch.dict('os.environ', {}, clear=True) + cls.mock_environ = mock.patch.dict("os.environ", {}, clear=True) cls.mock_environ.start() TestCase.setUpClass() @@ -47,6 +48,7 @@ def setUp(self): class TestRepl(BaseRepl): def _request_refresh(inner_self): pass + self.repl = TestRepl(config=setup_config()) self.repl.height, self.repl.width = (5, 10) @@ -59,8 +61,9 @@ def assert_paint(self, screen, cursor_row_col): self.assertFSArraysEqual(array, screen) self.assertEqual(cursor_pos, cursor_row_col) - def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None, - **paint_kwargs): + def assert_paint_ignoring_formatting( + self, screen, cursor_row_col=None, **paint_kwargs + ): array, cursor_pos = self.repl.paint(**paint_kwargs) self.assertFSArraysEqualIgnoringFormatting(array, screen) if cursor_row_col is not None: @@ -68,71 +71,97 @@ def assert_paint_ignoring_formatting(self, screen, cursor_row_col=None, class TestCurtsiesPaintingTest(CurtsiesPaintingTest): - def test_history_is_cleared(self): - self.assertEqual(self.repl.rl_history.entries, ['']) + self.assertEqual(self.repl.rl_history.entries, [""]) class TestCurtsiesPaintingSimple(CurtsiesPaintingTest): - def test_startup(self): - screen = fsarray([cyan('>>> '), cyan('Welcome to')]) + screen = fsarray([cyan(">>> "), cyan("Welcome to")]) self.assert_paint(screen, (0, 4)) def test_enter_text(self): - [self.repl.add_normal_character(c) for c in '1 + 1'] - screen = fsarray([cyan('>>> ') + bold(green('1') + cyan(' ') + - yellow('+') + cyan(' ') + green('1')), - cyan('Welcome to')]) + [self.repl.add_normal_character(c) for c in "1 + 1"] + screen = fsarray( + [ + cyan(">>> ") + + bold( + green("1") + + cyan(" ") + + yellow("+") + + cyan(" ") + + green("1") + ), + cyan("Welcome to"), + ] + ) self.assert_paint(screen, (0, 9)) def test_run_line(self): try: orig_stdout = sys.stdout sys.stdout = self.repl.stdout - [self.repl.add_normal_character(c) for c in '1 + 1'] + [self.repl.add_normal_character(c) for c in "1 + 1"] self.repl.on_enter(insert_into_history=False) - screen = fsarray(['>>> 1 + 1', '2', 'Welcome to']) + screen = fsarray([">>> 1 + 1", "2", "Welcome to"]) self.assert_paint_ignoring_formatting(screen, (1, 1)) finally: sys.stdout = orig_stdout def test_completion(self): self.repl.height, self.repl.width = (5, 32) - self.repl.current_line = 'an' + self.repl.current_line = "an" self.cursor_offset = 2 if config.supports_box_chars(): - screen = ['>>> an', - '┌──────────────────────────────┐', - '│ and any( │', - '└──────────────────────────────┘', - 'Welcome to bpython! Press f'] + screen = [ + ">>> an", + "┌──────────────────────────────┐", + "│ and any( │", + "└──────────────────────────────┘", + "Welcome to bpython! Press f", + ] else: - screen = ['>>> an', - '+------------------------------+', - '| and any( |', - '+------------------------------+', - 'Welcome to bpython! Press f'] + screen = [ + ">>> an", + "+------------------------------+", + "| and any( |", + "+------------------------------+", + "Welcome to bpython! Press f", + ] self.assert_paint_ignoring_formatting(screen, (0, 4)) def test_argspec(self): def foo(x, y, z=10): "docstring!" pass - argspec = inspection.getfuncprops('foo', foo) + + argspec = inspection.getfuncprops("foo", foo) array = replpainter.formatted_argspec(argspec, 1, 30, setup_config()) - screen = [bold(cyan('foo')) + cyan(':') + cyan(' ') + cyan('(') + - cyan('x') + yellow(',') + yellow(' ') + bold(cyan('y')) + - yellow(',') + yellow(' ') + cyan('z') + yellow('=') + - bold(cyan('10')) + yellow(')')] + screen = [ + bold(cyan("foo")) + + cyan(":") + + cyan(" ") + + cyan("(") + + cyan("x") + + yellow(",") + + yellow(" ") + + bold(cyan("y")) + + yellow(",") + + yellow(" ") + + cyan("z") + + yellow("=") + + bold(cyan("10")) + + yellow(")") + ] self.assertFSArraysEqual(fsarray(array), fsarray(screen)) def test_formatted_docstring(self): actual = replpainter.formatted_docstring( - 'Returns the results\n\n' 'Also has side effects', - 40, config=setup_config()) - expected = fsarray(['Returns the results', '', - 'Also has side effects']) + "Returns the results\n\n" "Also has side effects", + 40, + config=setup_config(), + ) + expected = fsarray(["Returns the results", "", "Also has side effects"]) self.assertFSArraysEqualIgnoringFormatting(actual, expected) def test_unicode_docstrings(self): @@ -140,20 +169,26 @@ def test_unicode_docstrings(self): # issue 653 def foo(): - u"åß∂ƒ" + "åß∂ƒ" actual = replpainter.formatted_docstring( - foo.__doc__, 40, config=setup_config()) - expected = fsarray([u'åß∂ƒ']) + foo.__doc__, 40, config=setup_config() + ) + expected = fsarray(["åß∂ƒ"]) self.assertFSArraysEqualIgnoringFormatting(actual, expected) def test_nonsense_docstrings(self): - for docstring in [123, {}, [], ]: + for docstring in [ + 123, + {}, + [], + ]: try: replpainter.formatted_docstring( - docstring, 40, config=setup_config()) + docstring, 40, config=setup_config() + ) except Exception: - self.fail('bad docstring caused crash: {!r}'.format(docstring)) + self.fail("bad docstring caused crash: {!r}".format(docstring)) def test_weird_boto_docstrings(self): # Boto does something like this. @@ -161,7 +196,7 @@ def test_weird_boto_docstrings(self): class WeirdDocstring(str): # a mighty hack. See botocore/docs/docstring.py def expandtabs(self, tabsize=8): - return u'asdfåß∂ƒ'.expandtabs(tabsize) + return "asdfåß∂ƒ".expandtabs(tabsize) def foo(): pass @@ -169,22 +204,17 @@ def foo(): foo.__doc__ = WeirdDocstring() wd = pydoc.getdoc(foo) actual = replpainter.formatted_docstring(wd, 40, config=setup_config()) - expected = fsarray([u'asdfåß∂ƒ']) + expected = fsarray(["asdfåß∂ƒ"]) self.assertFSArraysEqualIgnoringFormatting(actual, expected) def test_paint_lasts_events(self): - actual = replpainter.paint_last_events(4, 100, ['a', 'b', 'c'], - config=setup_config()) + actual = replpainter.paint_last_events( + 4, 100, ["a", "b", "c"], config=setup_config() + ) if config.supports_box_chars(): - expected = fsarray(["┌─┐", - "│c│", - "│b│", - "└─┘"]) + expected = fsarray(["┌─┐", "│c│", "│b│", "└─┘"]) else: - expected = fsarray(["+-+", - "|c|", - "|b|", - "+-+"]) + expected = fsarray(["+-+", "|c|", "|b|", "+-+"]) self.assertFSArraysEqualIgnoringFormatting(actual, expected) @@ -219,7 +249,7 @@ def enter(self, line=None): self.repl.current_line = line with output_to_repl(self.repl): self.repl.on_enter(insert_into_history=False) - self.assertEqual(self.repl.rl_history.entries, ['']) + self.assertEqual(self.repl.rl_history.entries, [""]) self.send_refreshes() def undo(self): @@ -233,103 +263,90 @@ def setUp(self): class TestRepl(BaseRepl): def _request_refresh(inner_self): self.refresh() - self.repl = TestRepl(banner='', config=setup_config()) + + self.repl = TestRepl(banner="", config=setup_config()) self.repl.height, self.repl.width = (5, 32) def send_key(self, key): - self.repl.process_event('' if key == ' ' else key) + self.repl.process_event("" if key == " " else key) self.repl.paint() # has some side effects we need to be wary of class TestCurtsiesRewindRedraw(HigherLevelCurtsiesPaintingTest): def test_rewind(self): - self.repl.current_line = '1 + 1' + self.repl.current_line = "1 + 1" self.enter() - screen = ['>>> 1 + 1', - '2', - '>>> '] + screen = [">>> 1 + 1", "2", ">>> "] self.assert_paint_ignoring_formatting(screen, (2, 4)) self.repl.undo() - screen = ['>>> '] + screen = [">>> "] self.assert_paint_ignoring_formatting(screen, (0, 4)) def test_rewind_contiguity_loss(self): - self.enter('1 + 1') - self.enter('2 + 2') - self.enter('def foo(x):') - self.repl.current_line = ' return x + 1' - screen = ['>>> 1 + 1', - '2', - '>>> 2 + 2', - '4', - '>>> def foo(x):', - '... return x + 1'] + self.enter("1 + 1") + self.enter("2 + 2") + self.enter("def foo(x):") + self.repl.current_line = " return x + 1" + screen = [ + ">>> 1 + 1", + "2", + ">>> 2 + 2", + "4", + ">>> def foo(x):", + "... return x + 1", + ] self.assert_paint_ignoring_formatting(screen, (5, 8)) self.repl.scroll_offset = 1 self.assert_paint_ignoring_formatting(screen[1:], (4, 8)) self.undo() - screen = ['2', - '>>> 2 + 2', - '4', - '>>> '] + screen = ["2", ">>> 2 + 2", "4", ">>> "] self.assert_paint_ignoring_formatting(screen, (3, 4)) self.undo() - screen = ['2', - '>>> '] + screen = ["2", ">>> "] self.assert_paint_ignoring_formatting(screen, (1, 4)) self.undo() - screen = [CONTIGUITY_BROKEN_MSG[:self.repl.width], - '>>> ', - '', - '', - '', - ' '] # TODO why is that there? Necessary? + screen = [ + CONTIGUITY_BROKEN_MSG[: self.repl.width], + ">>> ", + "", + "", + "", + " ", + ] # TODO why is that there? Necessary? self.assert_paint_ignoring_formatting(screen, (1, 4)) - screen = ['>>> '] + screen = [">>> "] self.assert_paint_ignoring_formatting(screen, (0, 4)) def test_inconsistent_history_doesnt_happen_if_onscreen(self): - self.enter('1 + 1') - screen = ['>>> 1 + 1', - '2', - '>>> '] + self.enter("1 + 1") + screen = [">>> 1 + 1", "2", ">>> "] self.assert_paint_ignoring_formatting(screen, (2, 4)) self.enter("2 + 2") - screen = ['>>> 1 + 1', - '2', - '>>> 2 + 2', - '4', - '>>> '] + screen = [">>> 1 + 1", "2", ">>> 2 + 2", "4", ">>> "] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.display_lines[0] = self.repl.display_lines[0] * 2 self.undo() - screen = ['>>> 1 + 1', - '2', - '>>> '] + screen = [">>> 1 + 1", "2", ">>> "] self.assert_paint_ignoring_formatting(screen, (2, 4)) def test_rewind_inconsistent_history(self): - self.enter('1 + 1') - self.enter('2 + 2') - self.enter('3 + 3') - screen = ['>>> 1 + 1', - '2', - '>>> 2 + 2', - '4', - '>>> 3 + 3', - '6', - '>>> '] + self.enter("1 + 1") + self.enter("2 + 2") + self.enter("3 + 3") + screen = [">>> 1 + 1", "2", ">>> 2 + 2", "4", ">>> 3 + 3", "6", ">>> "] self.assert_paint_ignoring_formatting(screen, (6, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[2:], (4, 4)) self.repl.display_lines[0] = self.repl.display_lines[0] * 2 self.undo() - screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], - '>>> 2 + 2', - '4', - '>>> ', - '', - ' '] + screen = [ + INCONSISTENT_HISTORY_MSG[: self.repl.width], + ">>> 2 + 2", + "4", + ">>> ", + "", + " ", + ] self.assert_paint_ignoring_formatting(screen, (3, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:-2], (2, 4)) @@ -338,39 +355,43 @@ def test_rewind_inconsistent_history(self): def test_rewind_inconsistent_history_more_lines_same_screen(self): self.repl.width = 60 sys.a = 5 - self.enter('import sys') - self.enter('for i in range(sys.a):') - self.enter(' print(sys.a)') - self.enter('') - self.enter('1 + 1') - self.enter('2 + 2') - screen = ['>>> import sys', - '>>> for i in range(sys.a):', - '... print(sys.a)', - '... ', - '5', - '5', - '5', - '5', - '5', - '>>> 1 + 1', - '2', - '>>> 2 + 2', - '4', - '>>> '] + self.enter("import sys") + self.enter("for i in range(sys.a):") + self.enter(" print(sys.a)") + self.enter("") + self.enter("1 + 1") + self.enter("2 + 2") + screen = [ + ">>> import sys", + ">>> for i in range(sys.a):", + "... print(sys.a)", + "... ", + "5", + "5", + "5", + "5", + "5", + ">>> 1 + 1", + "2", + ">>> 2 + 2", + "4", + ">>> ", + ] self.assert_paint_ignoring_formatting(screen, (13, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[9:], (4, 4)) sys.a = 6 self.undo() - screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], - '6', - # everything will jump down a line - that's perfectly - # reasonable - '>>> 1 + 1', - '2', - '>>> ', - ' '] + screen = [ + INCONSISTENT_HISTORY_MSG[: self.repl.width], + "6", + # everything will jump down a line - that's perfectly + # reasonable + ">>> 1 + 1", + "2", + ">>> ", + " ", + ] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:-1], (3, 4)) @@ -384,32 +405,36 @@ def test_rewind_inconsistent_history_more_lines_lower_screen(self): self.enter("") self.enter("1 + 1") self.enter("2 + 2") - screen = [">>> import sys", - ">>> for i in range(sys.a):", - "... print(sys.a)", - '... ', - '5', - '5', - '5', - '5', - '5', - '>>> 1 + 1', - '2', - '>>> 2 + 2', - '4', - '>>> '] + screen = [ + ">>> import sys", + ">>> for i in range(sys.a):", + "... print(sys.a)", + "... ", + "5", + "5", + "5", + "5", + "5", + ">>> 1 + 1", + "2", + ">>> 2 + 2", + "4", + ">>> ", + ] self.assert_paint_ignoring_formatting(screen, (13, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[9:], (4, 4)) sys.a = 8 self.undo() - screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], - '8', - '8', - '8', - '>>> 1 + 1', - '2', - '>>> '] + screen = [ + INCONSISTENT_HISTORY_MSG[: self.repl.width], + "8", + "8", + "8", + ">>> 1 + 1", + "2", + ">>> ", + ] self.assert_paint_ignoring_formatting(screen) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[-5:]) @@ -423,31 +448,35 @@ def test_rewind_inconsistent_history_more_lines_raise_screen(self): self.enter("") self.enter("1 + 1") self.enter("2 + 2") - screen = [">>> import sys", - ">>> for i in range(sys.a):", - "... print(sys.a)", - '... ', - '5', - '5', - '5', - '5', - '5', - '>>> 1 + 1', - '2', - '>>> 2 + 2', - '4', - '>>> '] + screen = [ + ">>> import sys", + ">>> for i in range(sys.a):", + "... print(sys.a)", + "... ", + "5", + "5", + "5", + "5", + "5", + ">>> 1 + 1", + "2", + ">>> 2 + 2", + "4", + ">>> ", + ] self.assert_paint_ignoring_formatting(screen, (13, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[9:], (4, 4)) sys.a = 1 self.undo() - screen = [INCONSISTENT_HISTORY_MSG[:self.repl.width], - '1', - '>>> 1 + 1', - '2', - '>>> ', - ' '] + screen = [ + INCONSISTENT_HISTORY_MSG[: self.repl.width], + "1", + ">>> 1 + 1", + "2", + ">>> ", + " ", + ] self.assert_paint_ignoring_formatting(screen) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:-1]) @@ -460,105 +489,101 @@ def test_rewind_history_not_quite_inconsistent(self): self.enter("") self.enter("1 + 1") self.enter("2 + 2") - screen = [">>> for i in range(__import__('sys').a):", - "... print(i)", - "... ", - '0', - '1', - '2', - '3', - '4', - '>>> 1 + 1', - '2', - '>>> 2 + 2', - '4', - '>>> '] + screen = [ + ">>> for i in range(__import__('sys').a):", + "... print(i)", + "... ", + "0", + "1", + "2", + "3", + "4", + ">>> 1 + 1", + "2", + ">>> 2 + 2", + "4", + ">>> ", + ] self.assert_paint_ignoring_formatting(screen, (12, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[8:], (4, 4)) sys.a = 6 self.undo() - screen = ['5', - # everything will jump down a line - that's perfectly - # reasonable - '>>> 1 + 1', - '2', - '>>> '] + screen = [ + "5", + # everything will jump down a line - that's perfectly + # reasonable + ">>> 1 + 1", + "2", + ">>> ", + ] self.assert_paint_ignoring_formatting(screen, (3, 4)) def test_rewind_barely_consistent(self): self.enter("1 + 1") self.enter("2 + 2") self.enter("3 + 3") - screen = [">>> 1 + 1", - '2', - '>>> 2 + 2', - '4', - '>>> 3 + 3', - '6', - '>>> '] + screen = [">>> 1 + 1", "2", ">>> 2 + 2", "4", ">>> 3 + 3", "6", ">>> "] self.assert_paint_ignoring_formatting(screen, (6, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[2:], (4, 4)) self.repl.display_lines[2] = self.repl.display_lines[2] * 2 self.undo() - screen = ['>>> 2 + 2', - '4', - '>>> '] + screen = [">>> 2 + 2", "4", ">>> "] self.assert_paint_ignoring_formatting(screen, (2, 4)) def test_clear_screen(self): self.enter("1 + 1") self.enter("2 + 2") - screen = [">>> 1 + 1", - '2', - '>>> 2 + 2', - '4', - '>>> '] + screen = [">>> 1 + 1", "2", ">>> 2 + 2", "4", ">>> "] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.request_paint_to_clear_screen = True - screen = [">>> 1 + 1", - '2', - '>>> 2 + 2', - '4', - '>>> ', '', '', '', ''] + screen = [">>> 1 + 1", "2", ">>> 2 + 2", "4", ">>> ", "", "", "", ""] self.assert_paint_ignoring_formatting(screen, (4, 4)) def test_scroll_down_while_banner_visible(self): - self.repl.status_bar.message('STATUS_BAR') + self.repl.status_bar.message("STATUS_BAR") self.enter("1 + 1") self.enter("2 + 2") - screen = [">>> 1 + 1", - '2', - '>>> 2 + 2', - '4', - '>>> ', - 'STATUS_BAR '] + screen = [ + ">>> 1 + 1", + "2", + ">>> 2 + 2", + "4", + ">>> ", + "STATUS_BAR ", + ] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:], (3, 4)) def test_clear_screen_while_banner_visible(self): - self.repl.status_bar.message('STATUS_BAR') + self.repl.status_bar.message("STATUS_BAR") self.enter("1 + 1") self.enter("2 + 2") - screen = [">>> 1 + 1", - '2', - '>>> 2 + 2', - '4', - '>>> ', - 'STATUS_BAR '] + screen = [ + ">>> 1 + 1", + "2", + ">>> 2 + 2", + "4", + ">>> ", + "STATUS_BAR ", + ] self.assert_paint_ignoring_formatting(screen, (4, 4)) self.repl.scroll_offset += len(screen) - self.repl.height self.assert_paint_ignoring_formatting(screen[1:], (3, 4)) self.repl.request_paint_to_clear_screen = True - screen = ['2', - '>>> 2 + 2', - '4', - '>>> ', - '', '', '', - 'STATUS_BAR '] + screen = [ + "2", + ">>> 2 + 2", + "4", + ">>> ", + "", + "", + "", + "STATUS_BAR ", + ] self.assert_paint_ignoring_formatting(screen, (3, 4)) def test_cursor_stays_at_bottom_of_screen(self): @@ -568,67 +593,71 @@ def test_cursor_stays_at_bottom_of_screen(self): self.repl.current_line = "__import__('random').__name__" with output_to_repl(self.repl): self.repl.on_enter(insert_into_history=False) - screen = [">>> __import__('random').__name__", - "'random'"] + screen = [">>> __import__('random').__name__", "'random'"] self.assert_paint_ignoring_formatting(screen) with output_to_repl(self.repl): self.repl.process_event(self.refresh_requests.pop()) - screen = [">>> __import__('random').__name__", - "'random'", - ""] + screen = [">>> __import__('random').__name__", "'random'", ""] self.assert_paint_ignoring_formatting(screen) with output_to_repl(self.repl): self.repl.process_event(self.refresh_requests.pop()) - screen = [">>> __import__('random').__name__", - "'random'", - ">>> "] + screen = [">>> __import__('random').__name__", "'random'", ">>> "] self.assert_paint_ignoring_formatting(screen, (2, 4)) def test_unhighlight_paren_bugs(self): """two previous bugs, parent didn't highlight until next render and paren didn't unhighlight until enter""" - self.assertEqual(self.repl.rl_history.entries, ['']) - self.enter('(') - self.assertEqual(self.repl.rl_history.entries, ['']) - screen = [">>> (", - "... "] - self.assertEqual(self.repl.rl_history.entries, ['']) + self.assertEqual(self.repl.rl_history.entries, [""]) + self.enter("(") + self.assertEqual(self.repl.rl_history.entries, [""]) + screen = [">>> (", "... "] + self.assertEqual(self.repl.rl_history.entries, [""]) self.assert_paint_ignoring_formatting(screen) - self.assertEqual(self.repl.rl_history.entries, ['']) + self.assertEqual(self.repl.rl_history.entries, [""]) with output_to_repl(self.repl): - self.assertEqual(self.repl.rl_history.entries, ['']) - self.repl.process_event(')') - self.assertEqual(self.repl.rl_history.entries, ['']) - screen = fsarray([cyan(">>> ") + on_magenta(bold(red('('))), - green("... ") + on_magenta(bold(red(')')))]) + self.assertEqual(self.repl.rl_history.entries, [""]) + self.repl.process_event(")") + self.assertEqual(self.repl.rl_history.entries, [""]) + screen = fsarray( + [ + cyan(">>> ") + on_magenta(bold(red("("))), + green("... ") + on_magenta(bold(red(")"))), + ] + ) self.assert_paint(screen, (1, 5)) with output_to_repl(self.repl): - self.repl.process_event(' ') - screen = fsarray([cyan(">>> ") + yellow('('), - green("... ") + yellow(')') + bold(cyan(" "))]) + self.repl.process_event(" ") + screen = fsarray( + [ + cyan(">>> ") + yellow("("), + green("... ") + yellow(")") + bold(cyan(" ")), + ] + ) self.assert_paint(screen, (1, 6)) def test_472(self): [self.send_key(c) for c in "(1, 2, 3)"] with output_to_repl(self.repl): - self.send_key('\n') + self.send_key("\n") self.send_refreshes() - self.send_key('') + self.send_key("") self.repl.paint() - [self.send_key('') for _ in range(4)] - self.send_key('') - self.send_key('4') + [self.send_key("") for _ in range(4)] + self.send_key("") + self.send_key("4") self.repl.on_enter() self.send_refreshes() - screen = [">>> (1, 2, 3)", - '(1, 2, 3)', - '>>> (1, 4, 3)', - '(1, 4, 3)', - '>>> '] + screen = [ + ">>> (1, 2, 3)", + "(1, 2, 3)", + ">>> (1, 4, 3)", + "(1, 4, 3)", + ">>> ", + ] self.assert_paint_ignoring_formatting(screen, (4, 4)) @@ -637,13 +666,13 @@ class Class(object): pass if chars_in_first_name < 1: - raise ValueError('need at least one char in each name') + raise ValueError("need at least one char in each name") elif chars_in_first_name == 1 and num_names > len(string.ascii_letters): - raise ValueError('need more chars to make so many names') + raise ValueError("need more chars to make so many names") names = gen_names() if num_names > 0: - setattr(Class, 'a' * chars_in_first_name, 1) + setattr(Class, "a" * chars_in_first_name, 1) next(names) # use the above instead of first name for _, name in zip(range(num_names - 1), names): setattr(Class, name, 0) @@ -653,129 +682,143 @@ class Class(object): def gen_names(): for letters in itertools.chain( - itertools.combinations_with_replacement(string.ascii_letters, 1), - itertools.combinations_with_replacement(string.ascii_letters, 2)): - yield ''.join(letters) + itertools.combinations_with_replacement(string.ascii_letters, 1), + itertools.combinations_with_replacement(string.ascii_letters, 2), + ): + yield "".join(letters) class TestCompletionHelpers(TestCase): def test_gen_names(self): - self.assertEqual(list(zip([1, 2, 3], gen_names())), - [(1, 'a'), (2, 'b'), (3, 'c')]) + self.assertEqual( + list(zip([1, 2, 3], gen_names())), [(1, "a"), (2, "b"), (3, "c")] + ) def test_completion_target(self): target = completion_target(14) - self.assertEqual(len([x for x in dir(target) - if not x.startswith('_')]), - 14) + self.assertEqual( + len([x for x in dir(target) if not x.startswith("_")]), 14 + ) class TestCurtsiesInfoboxPaint(HigherLevelCurtsiesPaintingTest): def test_simple(self): self.repl.width, self.repl.height = (20, 30) - self.locals['abc'] = completion_target(3, 50) - self.repl.current_line = 'abc' + self.locals["abc"] = completion_target(3, 50) + self.repl.current_line = "abc" self.repl.cursor_offset = 3 - self.repl.process_event('.') - screen = ['>>> abc.', - '+------------------+', - '| aaaaaaaaaaaaaaaa |', - '| b |', - '| c |', - '+------------------+'] + self.repl.process_event(".") + screen = [ + ">>> abc.", + "+------------------+", + "| aaaaaaaaaaaaaaaa |", + "| b |", + "| c |", + "+------------------+", + ] self.assert_paint_ignoring_formatting(screen, (0, 8)) def test_fill_screen(self): self.repl.width, self.repl.height = (20, 15) - self.locals['abc'] = completion_target(20, 100) - self.repl.current_line = 'abc' + self.locals["abc"] = completion_target(20, 100) + self.repl.current_line = "abc" self.repl.cursor_offset = 3 - self.repl.process_event('.') - screen = ['>>> abc.', - '+------------------+', - '| aaaaaaaaaaaaaaaa |', - '| b |', - '| c |', - '| d |', - '| e |', - '| f |', - '| g |', - '| h |', - '| i |', - '| j |', - '| k |', - '| l |', - '+------------------+'] + self.repl.process_event(".") + screen = [ + ">>> abc.", + "+------------------+", + "| aaaaaaaaaaaaaaaa |", + "| b |", + "| c |", + "| d |", + "| e |", + "| f |", + "| g |", + "| h |", + "| i |", + "| j |", + "| k |", + "| l |", + "+------------------+", + ] self.assert_paint_ignoring_formatting(screen, (0, 8)) def test_lower_on_screen(self): self.repl.get_top_usable_line = lambda: 10 # halfway down terminal self.repl.width, self.repl.height = (20, 15) - self.locals['abc'] = completion_target(20, 100) - self.repl.current_line = 'abc' + self.locals["abc"] = completion_target(20, 100) + self.repl.current_line = "abc" self.repl.cursor_offset = 3 - self.repl.process_event('.') - screen = ['>>> abc.', - '+------------------+', - '| aaaaaaaaaaaaaaaa |', - '| b |', - '| c |', - '| d |', - '| e |', - '| f |', - '| g |', - '| h |', - '| i |', - '| j |', - '| k |', - '| l |', - '+------------------+'] + self.repl.process_event(".") + screen = [ + ">>> abc.", + "+------------------+", + "| aaaaaaaaaaaaaaaa |", + "| b |", + "| c |", + "| d |", + "| e |", + "| f |", + "| g |", + "| h |", + "| i |", + "| j |", + "| k |", + "| l |", + "+------------------+", + ] # behavior before issue #466 self.assert_paint_ignoring_formatting( - screen, try_preserve_history_height=0) - self.assert_paint_ignoring_formatting( - screen, min_infobox_height=100) + screen, try_preserve_history_height=0 + ) + self.assert_paint_ignoring_formatting(screen, min_infobox_height=100) # behavior after issue #466 - screen = ['>>> abc.', - '+------------------+', - '| aaaaaaaaaaaaaaaa |', - '| b |', - '| c |', - '+------------------+'] + screen = [ + ">>> abc.", + "+------------------+", + "| aaaaaaaaaaaaaaaa |", + "| b |", + "| c |", + "+------------------+", + ] self.assert_paint_ignoring_formatting(screen) def test_at_bottom_of_screen(self): self.repl.get_top_usable_line = lambda: 17 # two lines from bottom self.repl.width, self.repl.height = (20, 15) - self.locals['abc'] = completion_target(20, 100) - self.repl.current_line = 'abc' + self.locals["abc"] = completion_target(20, 100) + self.repl.current_line = "abc" self.repl.cursor_offset = 3 - self.repl.process_event('.') - screen = ['>>> abc.', - '+------------------+', - '| aaaaaaaaaaaaaaaa |', - '| b |', - '| c |', - '| d |', - '| e |', - '| f |', - '| g |', - '| h |', - '| i |', - '| j |', - '| k |', - '| l |', - '+------------------+'] + self.repl.process_event(".") + screen = [ + ">>> abc.", + "+------------------+", + "| aaaaaaaaaaaaaaaa |", + "| b |", + "| c |", + "| d |", + "| e |", + "| f |", + "| g |", + "| h |", + "| i |", + "| j |", + "| k |", + "| l |", + "+------------------+", + ] # behavior before issue #466 self.assert_paint_ignoring_formatting( - screen, try_preserve_history_height=0) - self.assert_paint_ignoring_formatting( - screen, min_infobox_height=100) + screen, try_preserve_history_height=0 + ) + self.assert_paint_ignoring_formatting(screen, min_infobox_height=100) # behavior after issue #466 - screen = ['>>> abc.', - '+------------------+', - '| aaaaaaaaaaaaaaaa |', - '| b |', - '| c |', - '+------------------+'] + screen = [ + ">>> abc.", + "+------------------+", + "| aaaaaaaaaaaaaaaa |", + "| b |", + "| c |", + "+------------------+", + ] self.assert_paint_ignoring_formatting(screen) diff --git a/bpython/test/test_curtsies_parser.py b/bpython/test/test_curtsies_parser.py index 019a7e0d4..c0a20fcc0 100644 --- a/bpython/test/test_curtsies_parser.py +++ b/bpython/test/test_curtsies_parser.py @@ -8,17 +8,34 @@ class TestExecArgs(unittest.TestCase): - def test_parse(self): - self.assertEqual(parse.parse('\x01y\x03print\x04'), yellow('print')) + self.assertEqual(parse.parse("\x01y\x03print\x04"), yellow("print")) self.assertEqual( - parse.parse('\x01y\x03print\x04\x01c\x03 \x04\x01g\x031\x04\x01c' - '\x03 \x04\x01Y\x03+\x04\x01c\x03 \x04\x01g\x032\x04'), - yellow('print') + cyan(' ') + green('1') + cyan(' ') + - bold(yellow('+')) + cyan(' ') + green(u'2')) + parse.parse( + "\x01y\x03print\x04\x01c\x03 \x04\x01g\x031\x04\x01c" + "\x03 \x04\x01Y\x03+\x04\x01c\x03 \x04\x01g\x032\x04" + ), + yellow("print") + + cyan(" ") + + green("1") + + cyan(" ") + + bold(yellow("+")) + + cyan(" ") + + green("2"), + ) def test_peal_off_string(self): - self.assertEqual(parse.peel_off_string('\x01RI\x03]\x04asdf'), - ({'bg': 'I', 'string': ']', 'fg': 'R', 'colormarker': - '\x01RI', 'bold': ''}, 'asdf')) + self.assertEqual( + parse.peel_off_string("\x01RI\x03]\x04asdf"), + ( + { + "bg": "I", + "string": "]", + "fg": "R", + "colormarker": "\x01RI", + "bold": "", + }, + "asdf", + ), + ) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 330ed02d2..23f4b25b9 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -17,14 +17,20 @@ from bpython import config from bpython import args from bpython._py3compat import py3 -from bpython.test import (FixLanguageTestCase as TestCase, MagicIterMock, mock, - unittest, TEST_CONFIG) +from bpython.test import ( + FixLanguageTestCase as TestCase, + MagicIterMock, + mock, + unittest, + TEST_CONFIG, +) from curtsies import events if py3: from importlib import invalidate_caches else: + def invalidate_caches(): """Does not exist before Python 3.3""" @@ -34,72 +40,73 @@ def setup_config(conf): config.loadini(config_struct, TEST_CONFIG) for key, value in conf.items(): if not hasattr(config_struct, key): - raise ValueError("%r is not a valid config attribute" % (key, )) + raise ValueError("%r is not a valid config attribute" % (key,)) setattr(config_struct, key, value) return config_struct class TestCurtsiesRepl(TestCase): - def setUp(self): self.repl = create_repl() def cfwp(self, source): - return interpreter.code_finished_will_parse(source, - self.repl.interp.compile) + return interpreter.code_finished_will_parse( + source, self.repl.interp.compile + ) def test_code_finished_will_parse(self): - self.repl.buffer = ['1 + 1'] - self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) - self.repl.buffer = ['def foo(x):'] - self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (False, True)) - self.repl.buffer = ['def foo(x)'] - self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False)) - self.repl.buffer = ['def foo(x):', 'return 1'] - self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False)) - self.repl.buffer = ['def foo(x):', ' return 1'] - self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) - self.repl.buffer = ['def foo(x):', ' return 1', ''] - self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) + self.repl.buffer = ["1 + 1"] + self.assertTrue(self.cfwp("\n".join(self.repl.buffer)), (True, True)) + self.repl.buffer = ["def foo(x):"] + self.assertTrue(self.cfwp("\n".join(self.repl.buffer)), (False, True)) + self.repl.buffer = ["def foo(x)"] + self.assertTrue(self.cfwp("\n".join(self.repl.buffer)), (True, False)) + self.repl.buffer = ["def foo(x):", "return 1"] + self.assertTrue(self.cfwp("\n".join(self.repl.buffer)), (True, False)) + self.repl.buffer = ["def foo(x):", " return 1"] + self.assertTrue(self.cfwp("\n".join(self.repl.buffer)), (True, True)) + self.repl.buffer = ["def foo(x):", " return 1", ""] + self.assertTrue(self.cfwp("\n".join(self.repl.buffer)), (True, True)) def test_external_communication(self): self.repl.send_current_block_to_external_editor() self.repl.send_session_to_external_editor() - @unittest.skipUnless(all(map(config.can_encode, 'å∂߃')), - 'Charset can not encode characters') + @unittest.skipUnless( + all(map(config.can_encode, "å∂߃")), "Charset can not encode characters" + ) def test_external_communication_encoding(self): with captured_output(): self.repl.display_lines.append('>>> "åß∂ƒ"') self.repl.send_session_to_external_editor() def test_get_last_word(self): - self.repl.rl_history.entries = ['1', '2 3', '4 5 6'] - self.repl._set_current_line('abcde') + self.repl.rl_history.entries = ["1", "2 3", "4 5 6"] + self.repl._set_current_line("abcde") self.repl.get_last_word() - self.assertEqual(self.repl.current_line, 'abcde6') + self.assertEqual(self.repl.current_line, "abcde6") self.repl.get_last_word() - self.assertEqual(self.repl.current_line, 'abcde3') + self.assertEqual(self.repl.current_line, "abcde3") def test_last_word(self): - self.assertEqual(curtsiesrepl._last_word(''), '') - self.assertEqual(curtsiesrepl._last_word(' '), '') - self.assertEqual(curtsiesrepl._last_word('a'), 'a') - self.assertEqual(curtsiesrepl._last_word('a b'), 'b') + self.assertEqual(curtsiesrepl._last_word(""), "") + self.assertEqual(curtsiesrepl._last_word(" "), "") + self.assertEqual(curtsiesrepl._last_word("a"), "a") + self.assertEqual(curtsiesrepl._last_word("a b"), "b") # this is the behavior of bash - not currently implemented @unittest.skip def test_get_last_word_with_prev_line(self): - self.repl.rl_history.entries = ['1', '2 3', '4 5 6'] - self.repl._set_current_line('abcde') + self.repl.rl_history.entries = ["1", "2 3", "4 5 6"] + self.repl._set_current_line("abcde") self.repl.up_one_line() - self.assertEqual(self.repl.current_line, '4 5 6') + self.assertEqual(self.repl.current_line, "4 5 6") self.repl.get_last_word() - self.assertEqual(self.repl.current_line, '4 5 63') + self.assertEqual(self.repl.current_line, "4 5 63") self.repl.get_last_word() - self.assertEqual(self.repl.current_line, '4 5 64') + self.assertEqual(self.repl.current_line, "4 5 64") self.repl.up_one_line() - self.assertEqual(self.repl.current_line, '2 3') + self.assertEqual(self.repl.current_line, "2 3") def mock_next(obj, return_value): @@ -110,19 +117,19 @@ def mock_next(obj, return_value): class TestCurtsiesReplTab(TestCase): - def setUp(self): self.repl = create_repl() self.repl.matches_iter = MagicIterMock() def add_matches(*args, **kwargs): - self.repl.matches_iter.matches = ['aaa', 'aab', 'aac'] + self.repl.matches_iter.matches = ["aaa", "aab", "aac"] - self.repl.complete = mock.Mock(side_effect=add_matches, - return_value=True) + self.repl.complete = mock.Mock( + side_effect=add_matches, return_value=True + ) def test_tab_with_no_matches_triggers_completion(self): - self.repl._current_line = ' asdf' + self.repl._current_line = " asdf" self.repl._cursor_offset = 5 self.repl.matches_iter.matches = [] self.repl.matches_iter.is_cseq.return_value = False @@ -131,21 +138,21 @@ def test_tab_with_no_matches_triggers_completion(self): self.repl.complete.assert_called_once_with(tab=True) def test_tab_after_indentation_adds_space(self): - self.repl._current_line = ' ' + self.repl._current_line = " " self.repl._cursor_offset = 4 self.repl.on_tab() - self.assertEqual(self.repl._current_line, ' ') + self.assertEqual(self.repl._current_line, " ") self.assertEqual(self.repl._cursor_offset, 8) def test_tab_at_beginning_of_line_adds_space(self): - self.repl._current_line = '' + self.repl._current_line = "" self.repl._cursor_offset = 0 self.repl.on_tab() - self.assertEqual(self.repl._current_line, ' ') + self.assertEqual(self.repl._current_line, " ") self.assertEqual(self.repl._cursor_offset, 4) def test_tab_with_no_matches_selects_first(self): - self.repl._current_line = ' aa' + self.repl._current_line = " aa" self.repl._cursor_offset = 3 self.repl.matches_iter.matches = [] self.repl.matches_iter.is_cseq.return_value = False @@ -157,7 +164,7 @@ def test_tab_with_no_matches_selects_first(self): self.repl.matches_iter.cur_line.assert_called_once_with() def test_tab_with_matches_selects_next_match(self): - self.repl._current_line = ' aa' + self.repl._current_line = " aa" self.repl._cursor_offset = 3 self.repl.complete() self.repl.matches_iter.is_cseq.return_value = False @@ -167,9 +174,9 @@ def test_tab_with_matches_selects_next_match(self): self.repl.matches_iter.cur_line.assert_called_once_with() def test_tab_completes_common_sequence(self): - self.repl._current_line = ' a' + self.repl._current_line = " a" self.repl._cursor_offset = 2 - self.repl.matches_iter.matches = ['aaa', 'aab', 'aac'] + self.repl.matches_iter.matches = ["aaa", "aab", "aac"] self.repl.matches_iter.is_cseq.return_value = True self.repl.matches_iter.substitute_cseq.return_value = (None, None) self.repl.on_tab() @@ -183,21 +190,25 @@ def setUp(self): def test_list_win_visible_match_selected_on_tab_multiple_options(self): self.repl._current_line = " './'" self.repl._cursor_offset = 2 - with mock.patch('bpython.autocomplete.get_completer') as m: - m.return_value = (['./abc', './abcd', './bcd'], - autocomplete.FilenameCompletion()) + with mock.patch("bpython.autocomplete.get_completer") as m: + m.return_value = ( + ["./abc", "./abcd", "./bcd"], + autocomplete.FilenameCompletion(), + ) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() - self.assertEqual(self.repl.current_match, './abc') + self.assertEqual(self.repl.current_match, "./abc") self.assertEqual(self.repl.list_win_visible, True) def test_list_win_not_visible_and_cseq_if_cseq(self): self.repl._current_line = " './a'" self.repl._cursor_offset = 5 - with mock.patch('bpython.autocomplete.get_completer') as m: - m.return_value = (['./abcd', './abce'], - autocomplete.FilenameCompletion()) + with mock.patch("bpython.autocomplete.get_completer") as m: + m.return_value = ( + ["./abcd", "./abce"], + autocomplete.FilenameCompletion(), + ) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() @@ -208,8 +219,8 @@ def test_list_win_not_visible_and_cseq_if_cseq(self): def test_list_win_not_visible_and_match_selected_if_one_option(self): self.repl._current_line = " './a'" self.repl._cursor_offset = 5 - with mock.patch('bpython.autocomplete.get_completer') as m: - m.return_value = (['./abcd'], autocomplete.FilenameCompletion()) + with mock.patch("bpython.autocomplete.get_completer") as m: + m.return_value = (["./abcd"], autocomplete.FilenameCompletion()) self.repl.update_completion() self.assertEqual(self.repl.list_win_visible, False) self.repl.on_tab() @@ -231,37 +242,36 @@ def captured_output(): def create_repl(**kwargs): - config = setup_config({'editor': 'true'}) + config = setup_config({"editor": "true"}) repl = curtsiesrepl.BaseRepl(config=config, **kwargs) - os.environ['PAGER'] = 'true' - os.environ.pop('PYTHONSTARTUP', None) + os.environ["PAGER"] = "true" + os.environ.pop("PYTHONSTARTUP", None) repl.width = 50 repl.height = 20 return repl class TestFutureImports(TestCase): - def test_repl(self): repl = create_repl() with captured_output() as (out, err): - repl.push('from __future__ import division') - repl.push('1 / 2') - self.assertEqual(out.getvalue(), '0.5\n') + repl.push("from __future__ import division") + repl.push("1 / 2") + self.assertEqual(out.getvalue(), "0.5\n") def test_interactive(self): interp = code.InteractiveInterpreter(locals={}) with captured_output() as (out, err): - with tempfile.NamedTemporaryFile(mode='w', suffix='.py') as f: - f.write('from __future__ import division\n') - f.write('print(1/2)\n') + with tempfile.NamedTemporaryFile(mode="w", suffix=".py") as f: + f.write("from __future__ import division\n") + f.write("print(1/2)\n") f.flush() args.exec_code(interp, [f.name]) repl = create_repl(interp=interp) - repl.push('1 / 2') + repl.push("1 / 2") - self.assertEqual(out.getvalue(), '0.5\n0.5\n') + self.assertEqual(out.getvalue(), "0.5\n0.5\n") class TestStdOutErr(TestCase): @@ -269,27 +279,27 @@ def setUp(self): self.repl = create_repl() def test_newline(self): - self.repl.send_to_stdouterr('\n\n') - self.assertEqual(self.repl.display_lines[-2], '') - self.assertEqual(self.repl.display_lines[-1], '') - self.assertEqual(self.repl.current_stdouterr_line, '') + self.repl.send_to_stdouterr("\n\n") + self.assertEqual(self.repl.display_lines[-2], "") + self.assertEqual(self.repl.display_lines[-1], "") + self.assertEqual(self.repl.current_stdouterr_line, "") def test_leading_newline(self): - self.repl.send_to_stdouterr('\nfoo\n') - self.assertEqual(self.repl.display_lines[-2], '') - self.assertEqual(self.repl.display_lines[-1], 'foo') - self.assertEqual(self.repl.current_stdouterr_line, '') + self.repl.send_to_stdouterr("\nfoo\n") + self.assertEqual(self.repl.display_lines[-2], "") + self.assertEqual(self.repl.display_lines[-1], "foo") + self.assertEqual(self.repl.current_stdouterr_line, "") def test_no_trailing_newline(self): - self.repl.send_to_stdouterr('foo') - self.assertEqual(self.repl.current_stdouterr_line, 'foo') + self.repl.send_to_stdouterr("foo") + self.assertEqual(self.repl.current_stdouterr_line, "foo") def test_print_without_newline_then_print_with_leading_newline(self): - self.repl.send_to_stdouterr('foo') - self.repl.send_to_stdouterr('\nbar\n') - self.assertEqual(self.repl.display_lines[-2], 'foo') - self.assertEqual(self.repl.display_lines[-1], 'bar') - self.assertEqual(self.repl.current_stdouterr_line, '') + self.repl.send_to_stdouterr("foo") + self.repl.send_to_stdouterr("\nbar\n") + self.assertEqual(self.repl.display_lines[-2], "foo") + self.assertEqual(self.repl.display_lines[-1], "bar") + self.assertEqual(self.repl.current_stdouterr_line, "") class TestPredictedIndent(TestCase): @@ -297,16 +307,16 @@ def setUp(self): self.repl = create_repl() def test_simple(self): - self.assertEqual(self.repl.predicted_indent(''), 0) - self.assertEqual(self.repl.predicted_indent('class Foo:'), 4) - self.assertEqual(self.repl.predicted_indent('class Foo: pass'), 0) - self.assertEqual(self.repl.predicted_indent('def asdf():'), 4) - self.assertEqual(self.repl.predicted_indent('def asdf(): return 7'), 0) + self.assertEqual(self.repl.predicted_indent(""), 0) + self.assertEqual(self.repl.predicted_indent("class Foo:"), 4) + self.assertEqual(self.repl.predicted_indent("class Foo: pass"), 0) + self.assertEqual(self.repl.predicted_indent("def asdf():"), 4) + self.assertEqual(self.repl.predicted_indent("def asdf(): return 7"), 0) @unittest.skip def test_complex(self): - self.assertEqual(self.repl.predicted_indent('[a, '), 1) - self.assertEqual(self.repl.predicted_indent('reduce(asdfasdf, '), 7) + self.assertEqual(self.repl.predicted_indent("[a, "), 1) + self.assertEqual(self.repl.predicted_indent("reduce(asdfasdf, "), 7) class TestCurtsiesReevaluate(TestCase): @@ -314,17 +324,17 @@ def setUp(self): self.repl = create_repl() def test_variable_is_cleared(self): - self.repl._current_line = 'b = 10' + self.repl._current_line = "b = 10" self.repl.on_enter() - self.assertIn('b', self.repl.interp.locals) + self.assertIn("b", self.repl.interp.locals) self.repl.undo() - self.assertNotIn('b', self.repl.interp.locals) + self.assertNotIn("b", self.repl.interp.locals) class TestCurtsiesReevaluateWithImport(TestCase): def setUp(self): self.repl = create_repl() - self.open = partial(io.open, mode='wt', encoding='utf-8') + self.open = partial(io.open, mode="wt", encoding="utf-8") self.dont_write_bytecode = sys.dont_write_bytecode sys.dont_write_bytecode = True self.sys_path = sys.path @@ -353,132 +363,137 @@ def push(self, line): self.repl.on_enter() def head(self, path): - self.push('import sys') + self.push("import sys") self.push('sys.path.append("%s")' % (path)) @staticmethod @contextmanager def tempfile(): - with tempfile.NamedTemporaryFile(suffix='.py') as temp: + with tempfile.NamedTemporaryFile(suffix=".py") as temp: path, name = os.path.split(temp.name) - yield temp.name, path, name.replace('.py', '') + yield temp.name, path, name.replace(".py", "") def test_module_content_changed(self): with self.tempfile() as (fullpath, path, modname): print(modname) with self.open(fullpath) as f: - f.write('a = 0\n') + f.write("a = 0\n") self.head(path) - self.push('import %s' % (modname)) - self.push('a = %s.a' % (modname)) - self.assertIn('a', self.repl.interp.locals) - self.assertEqual(self.repl.interp.locals['a'], 0) + self.push("import %s" % (modname)) + self.push("a = %s.a" % (modname)) + self.assertIn("a", self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals["a"], 0) with self.open(fullpath) as f: - f.write('a = 1\n') + f.write("a = 1\n") self.repl.clear_modules_and_reevaluate() - self.assertIn('a', self.repl.interp.locals) - self.assertEqual(self.repl.interp.locals['a'], 1) + self.assertIn("a", self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals["a"], 1) def test_import_module_with_rewind(self): with self.tempfile() as (fullpath, path, modname): print(modname) with self.open(fullpath) as f: - f.write('a = 0\n') + f.write("a = 0\n") self.head(path) - self.push('import %s' % (modname)) + self.push("import %s" % (modname)) self.assertIn(modname, self.repl.interp.locals) self.repl.undo() self.assertNotIn(modname, self.repl.interp.locals) self.repl.clear_modules_and_reevaluate() self.assertNotIn(modname, self.repl.interp.locals) - self.push('import %s' % (modname)) - self.push('a = %s.a' % (modname)) - self.assertIn('a', self.repl.interp.locals) - self.assertEqual(self.repl.interp.locals['a'], 0) + self.push("import %s" % (modname)) + self.push("a = %s.a" % (modname)) + self.assertIn("a", self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals["a"], 0) with self.open(fullpath) as f: - f.write('a = 1\n') + f.write("a = 1\n") self.repl.clear_modules_and_reevaluate() - self.assertIn('a', self.repl.interp.locals) - self.assertEqual(self.repl.interp.locals['a'], 1) + self.assertIn("a", self.repl.interp.locals) + self.assertEqual(self.repl.interp.locals["a"], 1) class TestCurtsiesPagerText(TestCase): - def setUp(self): self.repl = create_repl() self.repl.pager = self.assert_pager_gets_unicode def assert_pager_gets_unicode(self, text): - self.assertIsInstance(text, type('')) + self.assertIsInstance(text, type("")) def test_help(self): self.repl.pager(self.repl.help_text()) - @unittest.skipUnless(all(map(config.can_encode, 'å∂߃')), - 'Charset can not encode characters') + @unittest.skipUnless( + all(map(config.can_encode, "å∂߃")), "Charset can not encode characters" + ) def test_show_source_not_formatted(self): self.repl.config.highlight_show_source = False - self.repl.get_source_of_current_name = lambda: 'source code å∂߃åß∂ƒ' + self.repl.get_source_of_current_name = lambda: "source code å∂߃åß∂ƒ" self.repl.show_source() - @unittest.skipUnless(all(map(config.can_encode, 'å∂߃')), - 'Charset can not encode characters') + @unittest.skipUnless( + all(map(config.can_encode, "å∂߃")), "Charset can not encode characters" + ) def test_show_source_formatted(self): self.repl.config.highlight_show_source = True - self.repl.get_source_of_current_name = lambda: 'source code å∂߃åß∂ƒ' + self.repl.get_source_of_current_name = lambda: "source code å∂߃åß∂ƒ" self.repl.show_source() class TestCurtsiesStartup(TestCase): - def setUp(self): self.repl = create_repl() def write_startup_file(self, fname, encoding): - with io.open(fname, mode='wt', encoding=encoding) as f: - f.write('# coding: ') + with io.open(fname, mode="wt", encoding=encoding) as f: + f.write("# coding: ") f.write(encoding) - f.write('\n') - f.write('from __future__ import unicode_literals\n') + f.write("\n") + f.write("from __future__ import unicode_literals\n") f.write('a = "äöü"\n') def test_startup_event_utf8(self): with tempfile.NamedTemporaryFile() as temp: - self.write_startup_file(temp.name, 'utf-8') - with mock.patch.dict('os.environ', {'PYTHONSTARTUP': temp.name}): + self.write_startup_file(temp.name, "utf-8") + with mock.patch.dict("os.environ", {"PYTHONSTARTUP": temp.name}): self.repl.process_event(bpythonevents.RunStartupFileEvent()) - self.assertIn('a', self.repl.interp.locals) + self.assertIn("a", self.repl.interp.locals) def test_startup_event_latin1(self): with tempfile.NamedTemporaryFile() as temp: - self.write_startup_file(temp.name, 'latin-1') - with mock.patch.dict('os.environ', {'PYTHONSTARTUP': temp.name}): + self.write_startup_file(temp.name, "latin-1") + with mock.patch.dict("os.environ", {"PYTHONSTARTUP": temp.name}): self.repl.process_event(bpythonevents.RunStartupFileEvent()) - self.assertIn('a', self.repl.interp.locals) + self.assertIn("a", self.repl.interp.locals) class TestCurtsiesPasteEvents(TestCase): - def setUp(self): self.repl = create_repl() def test_control_events_in_small_paste(self): - self.assertGreaterEqual(curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE, 6, - 'test assumes UI lag could cause 6 events') + self.assertGreaterEqual( + curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE, + 6, + "test assumes UI lag could cause 6 events", + ) p = events.PasteEvent() - p.events = ['a', 'b', 'c', 'd', '', 'e'] + p.events = ["a", "b", "c", "d", "", "e"] self.repl.process_event(p) - self.assertEqual(self.repl.current_line, 'eabcd') + self.assertEqual(self.repl.current_line, "eabcd") def test_control_events_in_large_paste(self): """Large paste events should ignore control characters""" p = events.PasteEvent() - p.events = (['a', ''] + - ['e'] * curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE) + p.events = ["a", ""] + [ + "e" + ] * curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE self.repl.process_event(p) - self.assertEqual(self.repl.current_line, - 'a' + 'e'*curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE) + self.assertEqual( + self.repl.current_line, + "a" + "e" * curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE, + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index 9fe56b02d..2a3b6f0de 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -5,6 +5,7 @@ try: import watchdog from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler + has_watchdog = True except ImportError: has_watchdog = False @@ -14,7 +15,6 @@ @unittest.skipUnless(has_watchdog, "watchdog required") class TestModuleChangeEventHandler(unittest.TestCase): - def setUp(self): self.module = ModuleChangedEventHandler([], 1) self.module.observer = mock.Mock() @@ -23,9 +23,11 @@ def test_create_module_handler(self): self.assertIsInstance(self.module, ModuleChangedEventHandler) def test_add_module(self): - self.module._add_module('something/test.py') - self.assertIn(os.path.abspath('something/test'), - self.module.dirs[os.path.abspath('something')]) + self.module._add_module("something/test.py") + self.assertIn( + os.path.abspath("something/test"), + self.module.dirs[os.path.abspath("something")], + ) def test_activate_throws_error_when_already_activated(self): self.module.activated = True diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index 01574bd29..f095104e3 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -12,7 +12,7 @@ class TestHistory(unittest.TestCase): def setUp(self): - self.history = History('#%d' % x for x in range(1000)) + self.history = History("#%d" % x for x in range(1000)) def test_is_at_start(self): self.history.first() @@ -42,89 +42,90 @@ def test_last(self): self.assertFalse(self.history.is_at_end) def test_back(self): - self.assertEqual(self.history.back(), '#999') - self.assertNotEqual(self.history.back(), '#999') - self.assertEqual(self.history.back(), '#997') + self.assertEqual(self.history.back(), "#999") + self.assertNotEqual(self.history.back(), "#999") + self.assertEqual(self.history.back(), "#997") for x in range(997): self.history.back() - self.assertEqual(self.history.back(), '#0') + self.assertEqual(self.history.back(), "#0") def test_forward(self): self.history.first() - self.assertEqual(self.history.forward(), '#1') - self.assertNotEqual(self.history.forward(), '#1') - self.assertEqual(self.history.forward(), '#3') + self.assertEqual(self.history.forward(), "#1") + self.assertNotEqual(self.history.forward(), "#1") + self.assertEqual(self.history.forward(), "#3") # 1000 == entries 4 == len(range(1, 3) ===> '#1000' (so +1) for x in range(1000 - 4 - 1): self.history.forward() - self.assertEqual(self.history.forward(), '#999') + self.assertEqual(self.history.forward(), "#999") def test_append(self): self.history.append('print "foo\n"\n') - self.history.append('\n') + self.history.append("\n") self.assertEqual(self.history.back(), 'print "foo\n"') def test_enter(self): - self.history.enter('#lastnumber!') + self.history.enter("#lastnumber!") - self.assertEqual(self.history.back(), '#lastnumber!') - self.assertEqual(self.history.forward(), '#lastnumber!') + self.assertEqual(self.history.back(), "#lastnumber!") + self.assertEqual(self.history.forward(), "#lastnumber!") def test_enter_2(self): - self.history.enter('#50') + self.history.enter("#50") - self.assertEqual(self.history.back(), '#509') - self.assertEqual(self.history.back(), '#508') - self.assertEqual(self.history.forward(), '#509') + self.assertEqual(self.history.back(), "#509") + self.assertEqual(self.history.back(), "#508") + self.assertEqual(self.history.forward(), "#509") def test_reset(self): - self.history.enter('#lastnumber!') + self.history.enter("#lastnumber!") self.history.reset() - self.assertEqual(self.history.back(), '#999') - self.assertEqual(self.history.forward(), '') + self.assertEqual(self.history.back(), "#999") + self.assertEqual(self.history.forward(), "") class TestHistoryFileAccess(unittest.TestCase): def setUp(self): - self.filename = 'history_temp_file' + self.filename = "history_temp_file" self.encoding = getpreferredencoding() - with io.open(self.filename, 'w', encoding=self.encoding, - errors='ignore') as f: - f.write(b'#1\n#2\n'.decode()) + with io.open( + self.filename, "w", encoding=self.encoding, errors="ignore" + ) as f: + f.write(b"#1\n#2\n".decode()) def test_load(self): history = History() history.load(self.filename, self.encoding) - self.assertEqual(history.entries, ['#1', '#2']) + self.assertEqual(history.entries, ["#1", "#2"]) def test_append_reload_and_write(self): history = History() - history.append_reload_and_write('#3', self.filename, self.encoding) - self.assertEqual(history.entries, ['#1', '#2', '#3']) + history.append_reload_and_write("#3", self.filename, self.encoding) + self.assertEqual(history.entries, ["#1", "#2", "#3"]) - history.append_reload_and_write('#4', self.filename, self.encoding) - self.assertEqual(history.entries, ['#1', '#2', '#3', '#4']) + history.append_reload_and_write("#4", self.filename, self.encoding) + self.assertEqual(history.entries, ["#1", "#2", "#3", "#4"]) def test_save(self): history = History() history.entries = [] - for line in ['#1', '#2', '#3', '#4']: + for line in ["#1", "#2", "#3", "#4"]: history.append_to(history.entries, line) # save only last 2 lines history.save(self.filename, self.encoding, lines=2) # empty the list of entries and load again from the file - history.entries = [''] + history.entries = [""] history.load(self.filename, self.encoding) - self.assertEqual(history.entries, ['#3', '#4']) + self.assertEqual(history.entries, ["#3", "#4"]) def tearDown(self): try: diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 4b0c0fca3..0de09e799 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -7,32 +7,38 @@ class TestSimpleComplete(unittest.TestCase): - def setUp(self): self.original_modules = importcompletion.modules - importcompletion.modules = ['zzabc', 'zzabd', 'zzefg', 'zzabc.e', - 'zzabc.f'] + importcompletion.modules = [ + "zzabc", + "zzabd", + "zzefg", + "zzabc.e", + "zzabc.f", + ] def tearDown(self): importcompletion.modules = self.original_modules def test_simple_completion(self): - self.assertSetEqual(importcompletion.complete(10, 'import zza'), - set(['zzabc', 'zzabd'])) + self.assertSetEqual( + importcompletion.complete(10, "import zza"), set(["zzabc", "zzabd"]) + ) def test_package_completion(self): - self.assertSetEqual(importcompletion.complete(13, 'import zzabc.'), - set(['zzabc.e', 'zzabc.f'])) + self.assertSetEqual( + importcompletion.complete(13, "import zzabc."), + set(["zzabc.e", "zzabc.f"]), + ) class TestRealComplete(unittest.TestCase): - @classmethod def setUpClass(cls): for _ in importcompletion.find_iterator: pass - __import__('sys') - __import__('os') + __import__("sys") + __import__("os") @classmethod def tearDownClass(cls): @@ -41,13 +47,15 @@ def tearDownClass(cls): def test_from_attribute(self): self.assertSetEqual( - importcompletion.complete(19, 'from sys import arg'), - set(['argv'])) + importcompletion.complete(19, "from sys import arg"), set(["argv"]) + ) def test_from_attr_module(self): - self.assertSetEqual(importcompletion.complete(9, 'from os.p'), - set(['os.path'])) + self.assertSetEqual( + importcompletion.complete(9, "from os.p"), set(["os.path"]) + ) def test_from_package(self): - self.assertSetEqual(importcompletion.complete(17, 'from xml import d'), - set(['dom'])) + self.assertSetEqual( + importcompletion.complete(17, "from xml import d"), set(["dom"]) + ) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index d860bea55..096ae4cb0 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -60,7 +60,7 @@ def test_is_callable(self): self.assertFalse(inspection.is_callable(None)) self.assertTrue(inspection.is_callable(CallableMethod().method)) - @unittest.skipIf(py3, 'old-style classes only exist in Python 2') + @unittest.skipIf(py3, "old-style classes only exist in Python 2") def test_is_new_style_py2(self): self.assertTrue(inspection.is_new_style(spam)) self.assertTrue(inspection.is_new_style(Noncallable)) @@ -69,7 +69,7 @@ def test_is_new_style_py2(self): self.assertFalse(inspection.is_new_style(OldNoncallable())) self.assertTrue(inspection.is_new_style(None)) - @unittest.skipUnless(py3, 'only in Python 3 are all classes new-style') + @unittest.skipUnless(py3, "only in Python 3 are all classes new-style") def test_is_new_style_py3(self): self.assertTrue(inspection.is_new_style(spam)) self.assertTrue(inspection.is_new_style(Noncallable)) @@ -78,17 +78,19 @@ def test_is_new_style_py3(self): self.assertTrue(inspection.is_new_style(OldNoncallable())) self.assertTrue(inspection.is_new_style(None)) - def test_parsekeywordpairs(self): # See issue #109 - def fails(spam=['-a', '-b']): + def fails(spam=["-a", "-b"]): pass default_arg_repr = "['-a', '-b']" - self.assertEqual(str(['-a', '-b']), default_arg_repr, - 'This test is broken (repr does not match), fix me.') + self.assertEqual( + str(["-a", "-b"]), + default_arg_repr, + "This test is broken (repr does not match), fix me.", + ) - argspec = inspection.getfuncprops('fails', fails) + argspec = inspection.getfuncprops("fails", fails) defaults = argspec.argspec.defaults self.assertEqual(str(defaults[0]), default_arg_repr) @@ -108,44 +110,50 @@ def spam(eggs=23, foobar="yay"): self.assertEqual(repr(defaults[1]), "'yay'") def test_get_encoding_ascii(self): - self.assertEqual(inspection.get_encoding(encoding_ascii), 'ascii') - self.assertEqual(inspection.get_encoding(encoding_ascii.foo), 'ascii') + self.assertEqual(inspection.get_encoding(encoding_ascii), "ascii") + self.assertEqual(inspection.get_encoding(encoding_ascii.foo), "ascii") def test_get_encoding_latin1(self): - self.assertEqual(inspection.get_encoding(encoding_latin1), 'latin1') - self.assertEqual(inspection.get_encoding(encoding_latin1.foo), - 'latin1') + self.assertEqual(inspection.get_encoding(encoding_latin1), "latin1") + self.assertEqual(inspection.get_encoding(encoding_latin1.foo), "latin1") def test_get_encoding_utf8(self): - self.assertEqual(inspection.get_encoding(encoding_utf8), 'utf-8') - self.assertEqual(inspection.get_encoding(encoding_utf8.foo), 'utf-8') + self.assertEqual(inspection.get_encoding(encoding_utf8), "utf-8") + self.assertEqual(inspection.get_encoding(encoding_utf8.foo), "utf-8") def test_get_source_ascii(self): - self.assertEqual(inspection.get_source_unicode(encoding_ascii.foo), - foo_ascii_only) + self.assertEqual( + inspection.get_source_unicode(encoding_ascii.foo), foo_ascii_only + ) def test_get_source_utf8(self): - self.assertEqual(inspection.get_source_unicode(encoding_utf8.foo), - foo_non_ascii) + self.assertEqual( + inspection.get_source_unicode(encoding_utf8.foo), foo_non_ascii + ) def test_get_source_latin1(self): - self.assertEqual(inspection.get_source_unicode(encoding_latin1.foo), - foo_non_ascii) + self.assertEqual( + inspection.get_source_unicode(encoding_latin1.foo), foo_non_ascii + ) def test_get_source_file(self): - path = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'fodder') + path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "fodder" + ) encoding = inspection.get_encoding_file( - os.path.join(path, 'encoding_ascii.py')) - self.assertEqual(encoding, 'ascii') + os.path.join(path, "encoding_ascii.py") + ) + self.assertEqual(encoding, "ascii") encoding = inspection.get_encoding_file( - os.path.join(path, 'encoding_latin1.py')) - self.assertEqual(encoding, 'latin1') + os.path.join(path, "encoding_latin1.py") + ) + self.assertEqual(encoding, "latin1") encoding = inspection.get_encoding_file( - os.path.join(path, 'encoding_utf8.py')) - self.assertEqual(encoding, 'utf-8') + os.path.join(path, "encoding_utf8.py") + ) + self.assertEqual(encoding, "utf-8") -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index d25d9d7d2..597ae8832 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -12,11 +12,11 @@ from bpython._py3compat import py3 from bpython.test import mock, unittest -pypy = 'PyPy' in sys.version +pypy = "PyPy" in sys.version def remove_ansi(s): - return re.sub(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]'.encode('ascii'), b'', s) + return re.sub(r"(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]".encode("ascii"), b"", s) class TestInterpreter(unittest.TestCase): @@ -30,7 +30,7 @@ def err_lineno(self, a): strings = [x.__unicode__() for x in a] for line in reversed(strings): clean_line = remove_ansi(line) - m = re.search(r'line (\d+)[,]', clean_line) + m = re.search(r"line (\d+)[,]", clean_line) if m: return int(m.group(1)) return None @@ -38,23 +38,35 @@ def err_lineno(self, a): def test_syntaxerror(self): i, a = self.interp_errlog() - i.runsource('1.1.1.1') + i.runsource("1.1.1.1") if pypy: expected = ( - ' File ' + green('""') + - ', line ' + bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + - bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + - '\n') + " File " + + green('""') + + ", line " + + bold(magenta("1")) + + "\n 1.1.1.1\n ^\n" + + bold(red("SyntaxError")) + + ": " + + cyan("invalid syntax") + + "\n" + ) else: expected = ( - ' File ' + green('""') + - ', line ' + bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + - bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + - '\n') - - self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) - self.assertEqual(plain('').join(a), expected) + " File " + + green('""') + + ", line " + + bold(magenta("1")) + + "\n 1.1.1.1\n ^\n" + + bold(red("SyntaxError")) + + ": " + + cyan("invalid syntax") + + "\n" + ) + + self.assertMultiLineEqual(str(plain("").join(a)), str(expected)) + self.assertEqual(plain("").join(a), expected) def test_traceback(self): i, a = self.interp_errlog() @@ -65,7 +77,7 @@ def f(): def gfunc(): return f() - i.runsource('gfunc()') + i.runsource("gfunc()") if pypy and not py3: global_not_found = "global name 'gfunc' is not defined" @@ -73,29 +85,37 @@ def gfunc(): global_not_found = "name 'gfunc' is not defined" expected = ( - 'Traceback (most recent call last):\n File ' + - green('""') + ', line ' + - bold(magenta('1')) + ', in ' + cyan('') + '\n gfunc()\n' + - bold(red('NameError')) + ': ' + cyan(global_not_found) + '\n') - - self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) - self.assertEqual(plain('').join(a), expected) + "Traceback (most recent call last):\n File " + + green('""') + + ", line " + + bold(magenta("1")) + + ", in " + + cyan("") + + "\n gfunc()\n" + + bold(red("NameError")) + + ": " + + cyan(global_not_found) + + "\n" + ) + + self.assertMultiLineEqual(str(plain("").join(a)), str(expected)) + self.assertEqual(plain("").join(a), expected) @unittest.skipIf(py3, "runsource() accepts only unicode in Python 3") def test_runsource_bytes(self): - i = interpreter.Interp(encoding=b'latin-1') + i = interpreter.Interp(encoding=b"latin-1") - i.runsource("a = b'\xfe'".encode('latin-1'), encode=False) - self.assertIsInstance(i.locals['a'], str) - self.assertEqual(i.locals['a'], b"\xfe") + i.runsource("a = b'\xfe'".encode("latin-1"), encode=False) + self.assertIsInstance(i.locals["a"], str) + self.assertEqual(i.locals["a"], b"\xfe") - i.runsource("b = u'\xfe'".encode('latin-1'), encode=False) - self.assertIsInstance(i.locals['b'], unicode) - self.assertEqual(i.locals['b'], "\xfe") + i.runsource("b = u'\xfe'".encode("latin-1"), encode=False) + self.assertIsInstance(i.locals["b"], unicode) + self.assertEqual(i.locals["b"], "\xfe") @unittest.skipUnless(py3, "Only a syntax error in Python 3") def test_runsource_bytes_over_128_syntax_error_py3(self): - i = interpreter.Interp(encoding=b'latin-1') + i = interpreter.Interp(encoding=b"latin-1") i.showsyntaxerror = mock.Mock(return_value=None) i.runsource("a = b'\xfe'") @@ -103,26 +123,27 @@ def test_runsource_bytes_over_128_syntax_error_py3(self): @unittest.skipIf(py3, "encode is Python 2 only") def test_runsource_bytes_over_128_syntax_error_py2(self): - i = interpreter.Interp(encoding=b'latin-1') + i = interpreter.Interp(encoding=b"latin-1") i.runsource(b"a = b'\xfe'") - self.assertIsInstance(i.locals['a'], type(b'')) - self.assertEqual(i.locals['a'], b"\xfe") + self.assertIsInstance(i.locals["a"], type(b"")) + self.assertEqual(i.locals["a"], b"\xfe") @unittest.skipIf(py3, "encode is Python 2 only") def test_runsource_unicode(self): - i = interpreter.Interp(encoding=b'latin-1') + i = interpreter.Interp(encoding=b"latin-1") i.runsource("a = u'\xfe'") - self.assertIsInstance(i.locals['a'], type(u'')) - self.assertEqual(i.locals['a'], u"\xfe") + self.assertIsInstance(i.locals["a"], type("")) + self.assertEqual(i.locals["a"], "\xfe") def test_getsource_works_on_interactively_defined_functions(self): - source = 'def foo(x):\n return x + 1\n' + source = "def foo(x):\n return x + 1\n" i = interpreter.Interp() i.runsource(source) import inspect - inspected_source = inspect.getsource(i.locals['foo']) + + inspected_source = inspect.getsource(i.locals["foo"]) self.assertEqual(inspected_source, source) @unittest.skipIf(py3, "encode only does anything in Python 2") @@ -131,113 +152,148 @@ def test_runsource_unicode_autoencode_and_noencode(self): # Since correct behavior for unicode is the same # for auto and False, run the same tests - for encode in ['auto', False]: + for encode in ["auto", False]: i, a = self.interp_errlog() - i.runsource(u'[1 + 1,\nabcd]', encode=encode) + i.runsource("[1 + 1,\nabcd]", encode=encode) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(u'[1 + 1,\nabcd]', encode=encode) + i.runsource("[1 + 1,\nabcd]", encode=encode) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(u'#encoding: utf-8\nabcd', encode=encode) + i.runsource("#encoding: utf-8\nabcd", encode=encode) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(u'#encoding: utf-8\nabcd', - filename='x.py', encode=encode) - self.assertIn('SyntaxError:', - ''.join(''.join(remove_ansi(x.__unicode__()) - for x in a))) + i.runsource( + "#encoding: utf-8\nabcd", filename="x.py", encode=encode + ) + self.assertIn( + "SyntaxError:", + "".join("".join(remove_ansi(x.__unicode__()) for x in a)), + ) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_unicode_encode(self): i, _ = self.interp_errlog() with self.assertRaises(ValueError): - i.runsource(u'1 + 1', encode=True) + i.runsource("1 + 1", encode=True) i, _ = self.interp_errlog() with self.assertRaises(ValueError): - i.runsource(u'1 + 1', filename='x.py', encode=True) + i.runsource("1 + 1", filename="x.py", encode=True) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_bytestring_noencode(self): i, a = self.interp_errlog() - i.runsource(b'[1 + 1,\nabcd]', encode=False) + i.runsource(b"[1 + 1,\nabcd]", encode=False) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(b'[1 + 1,\nabcd]', filename='x.py', encode=False) + i.runsource(b"[1 + 1,\nabcd]", filename="x.py", encode=False) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(dedent(b'''\ + i.runsource( + dedent( + b"""\ #encoding: utf-8 ["%s", - abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=False) + abcd]""" + % ("åß∂ƒ".encode("utf8"),) + ), + encode=False, + ) self.assertEqual(self.err_lineno(a), 4) i, a = self.interp_errlog() - i.runsource(dedent(b'''\ + i.runsource( + dedent( + b"""\ #encoding: utf-8 ["%s", - abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), - filename='x.py', encode=False) + abcd]""" + % ("åß∂ƒ".encode("utf8"),) + ), + filename="x.py", + encode=False, + ) self.assertEqual(self.err_lineno(a), 4) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_bytestring_encode(self): i, a = self.interp_errlog() - i.runsource(b'[1 + 1,\nabcd]', encode=True) + i.runsource(b"[1 + 1,\nabcd]", encode=True) self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() with self.assertRaises(ValueError): - i.runsource(b'[1 + 1,\nabcd]', filename='x.py', encode=True) + i.runsource(b"[1 + 1,\nabcd]", filename="x.py", encode=True) i, a = self.interp_errlog() - i.runsource(dedent(b'''\ + i.runsource( + dedent( + b"""\ #encoding: utf-8 [u"%s", - abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=True) + abcd]""" + % ("åß∂ƒ".encode("utf8"),) + ), + encode=True, + ) self.assertEqual(self.err_lineno(a), 4) i, a = self.interp_errlog() with self.assertRaises(ValueError): - i.runsource(dedent(b'''\ + i.runsource( + dedent( + b"""\ #encoding: utf-8 [u"%s", - abcd]''' % (u'åß∂ƒ'.encode('utf8'),)), - filename='x.py', - encode=True) + abcd]""" + % ("åß∂ƒ".encode("utf8"),) + ), + filename="x.py", + encode=True, + ) @unittest.skipIf(py3, "encode only does anything in Python 2") def test_runsource_bytestring_autoencode(self): i, a = self.interp_errlog() - i.runsource(b'[1 + 1,\n abcd]') + i.runsource(b"[1 + 1,\n abcd]") self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(b'[1 + 1,\nabcd]', filename='x.py') + i.runsource(b"[1 + 1,\nabcd]", filename="x.py") self.assertEqual(self.err_lineno(a), 2) i, a = self.interp_errlog() - i.runsource(dedent(b'''\ + i.runsource( + dedent( + b"""\ #encoding: utf-8 [u"%s", - abcd]''' % (u'åß∂ƒ'.encode('utf8'),))) + abcd]""" + % ("åß∂ƒ".encode("utf8"),) + ) + ) self.assertEqual(self.err_lineno(a), 4) i, a = self.interp_errlog() - i.runsource(dedent(b'''\ + i.runsource( + dedent( + b"""\ #encoding: utf-8 [u"%s", - abcd]''' % (u'åß∂ƒ'.encode('utf8'),))) + abcd]""" + % ("åß∂ƒ".encode("utf8"),) + ) + ) self.assertEqual(self.err_lineno(a), 4) diff --git a/bpython/test/test_keys.py b/bpython/test/test_keys.py index 1e19f8e5f..75f62353c 100644 --- a/bpython/test/test_keys.py +++ b/bpython/test/test_keys.py @@ -12,27 +12,27 @@ def test_keymap_map(self): def test_keymap_setitem(self): """Verify keys.KeyMap correctly setting items.""" - keys.cli_key_dispatch['simon'] = 'awesome' - self.assertEqual(keys.cli_key_dispatch['simon'], 'awesome') + keys.cli_key_dispatch["simon"] = "awesome" + self.assertEqual(keys.cli_key_dispatch["simon"], "awesome") def test_keymap_delitem(self): """Verify keys.KeyMap correctly removing items.""" - keys.cli_key_dispatch['simon'] = 'awesome' - del keys.cli_key_dispatch['simon'] - if 'simon' in keys.cli_key_dispatch.map: - raise Exception('Key still exists in dictionary') + keys.cli_key_dispatch["simon"] = "awesome" + del keys.cli_key_dispatch["simon"] + if "simon" in keys.cli_key_dispatch.map: + raise Exception("Key still exists in dictionary") def test_keymap_getitem(self): """Verify keys.KeyMap correctly looking up items.""" - self.assertEqual(keys.cli_key_dispatch['C-['], (chr(27), '^[')) - self.assertEqual(keys.cli_key_dispatch['F11'], ('KEY_F(11)',)) - self.assertEqual(keys.cli_key_dispatch['C-a'], ('\x01', '^A')) + self.assertEqual(keys.cli_key_dispatch["C-["], (chr(27), "^[")) + self.assertEqual(keys.cli_key_dispatch["F11"], ("KEY_F(11)",)) + self.assertEqual(keys.cli_key_dispatch["C-a"], ("\x01", "^A")) def test_keymap_keyerror(self): """Verify keys.KeyMap raising KeyError when getting undefined key""" with self.assertRaises(KeyError): - keys.cli_key_dispatch['C-asdf'] - keys.cli_key_dispatch['C-qwerty'] + keys.cli_key_dispatch["C-asdf"] + keys.cli_key_dispatch["C-qwerty"] class TestUrwidKeys(unittest.TestCase): @@ -43,28 +43,28 @@ def test_keymap_map(self): def test_keymap_setitem(self): """Verify keys.KeyMap correctly setting items.""" - keys.urwid_key_dispatch['simon'] = 'awesome' - self.assertEqual(keys.urwid_key_dispatch['simon'], 'awesome') + keys.urwid_key_dispatch["simon"] = "awesome" + self.assertEqual(keys.urwid_key_dispatch["simon"], "awesome") def test_keymap_delitem(self): """Verify keys.KeyMap correctly removing items.""" - keys.urwid_key_dispatch['simon'] = 'awesome' - del keys.urwid_key_dispatch['simon'] - if 'simon' in keys.urwid_key_dispatch.map: - raise Exception('Key still exists in dictionary') + keys.urwid_key_dispatch["simon"] = "awesome" + del keys.urwid_key_dispatch["simon"] + if "simon" in keys.urwid_key_dispatch.map: + raise Exception("Key still exists in dictionary") def test_keymap_getitem(self): """Verify keys.KeyMap correctly looking up items.""" - self.assertEqual(keys.urwid_key_dispatch['F11'], 'f11') - self.assertEqual(keys.urwid_key_dispatch['C-a'], 'ctrl a') - self.assertEqual(keys.urwid_key_dispatch['M-a'], 'meta a') + self.assertEqual(keys.urwid_key_dispatch["F11"], "f11") + self.assertEqual(keys.urwid_key_dispatch["C-a"], "ctrl a") + self.assertEqual(keys.urwid_key_dispatch["M-a"], "meta a") def test_keymap_keyerror(self): """Verify keys.KeyMap raising KeyError when getting undefined key""" with self.assertRaises(KeyError): - keys.urwid_key_dispatch['C-asdf'] - keys.urwid_key_dispatch['C-qwerty'] + keys.urwid_key_dispatch["C-asdf"] + keys.urwid_key_dispatch["C-qwerty"] -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index cffd57241..35169c6ac 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -3,43 +3,53 @@ import re from bpython.test import unittest -from bpython.line import current_word, current_dict_key, current_dict, \ - current_string, current_object, current_object_attribute, \ - current_from_import_from, current_from_import_import, current_import, \ - current_method_definition_name, current_single_word, \ - current_expression_attribute, current_dotted_attribute +from bpython.line import ( + current_word, + current_dict_key, + current_dict, + current_string, + current_object, + current_object_attribute, + current_from_import_from, + current_from_import_import, + current_import, + current_method_definition_name, + current_single_word, + current_expression_attribute, + current_dotted_attribute, +) def cursor(s): """'ab|c' -> (2, 'abc')""" - cursor_offset = s.index('|') - line = s[:cursor_offset] + s[cursor_offset + 1:] + cursor_offset = s.index("|") + line = s[:cursor_offset] + s[cursor_offset + 1 :] return cursor_offset, line def decode(s): """'ad' -> ((3, 'abcd'), (1, 3, 'bdc'))""" - if not s.count('|') == 1: - raise ValueError('match helper needs | to occur once') - if s.count('<') != s.count('>') or s.count('<') not in (0, 1): - raise ValueError('match helper needs <, and > to occur just once') - matches = list(re.finditer(r'[<>|]', s)) + if not s.count("|") == 1: + raise ValueError("match helper needs | to occur once") + if s.count("<") != s.count(">") or s.count("<") not in (0, 1): + raise ValueError("match helper needs <, and > to occur just once") + matches = list(re.finditer(r"[<>|]", s)) assert len(matches) in [1, 3], [m.group() for m in matches] d = {} for i, m in enumerate(matches): d[m.group(0)] = m.start() - i - s = s[:m.start() - i] + s[m.end() - i:] - assert len(d) in [1, 3], 'need all the parts just once! %r' % d + s = s[: m.start() - i] + s[m.end() - i :] + assert len(d) in [1, 3], "need all the parts just once! %r" % d - if '<' in d: - return (d['|'], s), (d['<'], d['>'], s[d['<']:d['>']]) + if "<" in d: + return (d["|"], s), (d["<"], d[">"], s[d["<"] : d[">"]]) else: - return (d['|'], s), None + return (d["|"], s), None def line_with_cursor(cursor_offset, line): - return line[:cursor_offset] + '|' + line[cursor_offset:] + return line[:cursor_offset] + "|" + line[cursor_offset:] def encode(cursor_offset, line, result): @@ -53,14 +63,15 @@ def encode(cursor_offset, line, result): start, end, value = result assert line[start:end] == value if start < cursor_offset: - encoded_line = encoded_line[:start] + '<' + encoded_line[start:] + encoded_line = encoded_line[:start] + "<" + encoded_line[start:] else: - encoded_line = (encoded_line[:start + 1] + '<' + - encoded_line[start + 1:]) + encoded_line = ( + encoded_line[: start + 1] + "<" + encoded_line[start + 1 :] + ) if end < cursor_offset: - encoded_line = encoded_line[:end + 1] + '>' + encoded_line[end + 1:] + encoded_line = encoded_line[: end + 1] + ">" + encoded_line[end + 1 :] else: - encoded_line = encoded_line[:end + 2] + '>' + encoded_line[end + 2:] + encoded_line = encoded_line[: end + 2] + ">" + encoded_line[end + 2 :] return encoded_line @@ -79,32 +90,41 @@ def assertAccess(self, s): result = self.func(cursor_offset, line) self.assertEqual( - result, match, - "%s(%r) result\n%r (%r) doesn't match expected\n%r (%r)" % ( - self.func.__name__, line_with_cursor(cursor_offset, line), - encode(cursor_offset, line, result), result, s, match)) + result, + match, + "%s(%r) result\n%r (%r) doesn't match expected\n%r (%r)" + % ( + self.func.__name__, + line_with_cursor(cursor_offset, line), + encode(cursor_offset, line, result), + result, + s, + match, + ), + ) class TestHelpers(LineTestCase): def test_I(self): - self.assertEqual(cursor('asd|fgh'), (3, 'asdfgh')) + self.assertEqual(cursor("asd|fgh"), (3, "asdfgh")) def test_decode(self): - self.assertEqual(decode('ad'), ((3, 'abdcd'), (1, 4, 'bdc'))) - self.assertEqual(decode('a|d'), ((1, 'abdcd'), (1, 4, 'bdc'))) - self.assertEqual(decode('ad|'), ((5, 'abdcd'), (1, 4, 'bdc'))) + self.assertEqual(decode("ad"), ((3, "abdcd"), (1, 4, "bdc"))) + self.assertEqual(decode("a|d"), ((1, "abdcd"), (1, 4, "bdc"))) + self.assertEqual(decode("ad|"), ((5, "abdcd"), (1, 4, "bdc"))) def test_encode(self): - self.assertEqual(encode(3, 'abdcd', (1, 4, 'bdc')), 'ad') - self.assertEqual(encode(1, 'abdcd', (1, 4, 'bdc')), 'a|d') - self.assertEqual(encode(4, 'abdcd', (1, 4, 'bdc')), 'ad') - self.assertEqual(encode(5, 'abdcd', (1, 4, 'bdc')), 'ad|') + self.assertEqual(encode(3, "abdcd", (1, 4, "bdc")), "ad") + self.assertEqual(encode(1, "abdcd", (1, 4, "bdc")), "a|d") + self.assertEqual(encode(4, "abdcd", (1, 4, "bdc")), "ad") + self.assertEqual(encode(5, "abdcd", (1, 4, "bdc")), "ad|") def test_assert_access(self): def dumb_func(cursor_offset, line): - return (0, 2, 'ab') + return (0, 2, "ab") + self.func = dumb_func - self.assertAccess('d') + self.assertAccess("d") class TestCurrentWord(LineTestCase): @@ -112,37 +132,37 @@ def setUp(self): self.func = current_word def test_simple(self): - self.assertAccess('|') - self.assertAccess('|asdf') - self.assertAccess('') - self.assertAccess('') - self.assertAccess('') - self.assertAccess('asdf + ') - self.assertAccess(' + asdf') + self.assertAccess("|") + self.assertAccess("|asdf") + self.assertAccess("") + self.assertAccess("") + self.assertAccess("") + self.assertAccess("asdf + ") + self.assertAccess(" + asdf") def test_inside(self): - self.assertAccess('') - self.assertAccess('') + self.assertAccess("") + self.assertAccess("") def test_dots(self): - self.assertAccess('') - self.assertAccess('') - self.assertAccess('') - self.assertAccess('stuff[stuff] + {123: 456} + ') - self.assertAccess('stuff[]') - self.assertAccess('stuff[asdf[]') + self.assertAccess("") + self.assertAccess("") + self.assertAccess("") + self.assertAccess("stuff[stuff] + {123: 456} + ") + self.assertAccess("stuff[]") + self.assertAccess("stuff[asdf[]") def test_non_dots(self): - self.assertAccess('].asdf|') - self.assertAccess(').asdf|') - self.assertAccess('foo[0].asdf|') - self.assertAccess('foo().asdf|') - self.assertAccess('foo().|') - self.assertAccess('foo().asdf.|') - self.assertAccess('foo[0].asdf.|') + self.assertAccess("].asdf|") + self.assertAccess(").asdf|") + self.assertAccess("foo[0].asdf|") + self.assertAccess("foo().asdf|") + self.assertAccess("foo().|") + self.assertAccess("foo().asdf.|") + self.assertAccess("foo[0].asdf.|") def test_open_paren(self): - self.assertAccess('') + self.assertAccess("") # documenting current behavior - TODO is this intended? @@ -151,17 +171,17 @@ def setUp(self): self.func = current_dict_key def test_simple(self): - self.assertAccess('asdf|') - self.assertAccess('asdf|') - self.assertAccess('asdf[<>|') - self.assertAccess('asdf[<>|]') - self.assertAccess('object.dict[') - self.assertAccess('asdf|') - self.assertAccess('asdf[<(>|]') - self.assertAccess('asdf[<(1>|]') - self.assertAccess('asdf[<(1,>|]') - self.assertAccess('asdf[<(1, >|]') - self.assertAccess('asdf[<(1, 2)>|]') + self.assertAccess("asdf|") + self.assertAccess("asdf|") + self.assertAccess("asdf[<>|") + self.assertAccess("asdf[<>|]") + self.assertAccess("object.dict[") + self.assertAccess("asdf|") + self.assertAccess("asdf[<(>|]") + self.assertAccess("asdf[<(1>|]") + self.assertAccess("asdf[<(1,>|]") + self.assertAccess("asdf[<(1, >|]") + self.assertAccess("asdf[<(1, 2)>|]") # TODO self.assertAccess('d[d[<12|>') self.assertAccess("d[<'a>|") @@ -171,12 +191,12 @@ def setUp(self): self.func = current_dict def test_simple(self): - self.assertAccess('asdf|') - self.assertAccess('asdf|') - self.assertAccess('[|') - self.assertAccess('[|]') - self.assertAccess('[abc|') - self.assertAccess('asdf|') + self.assertAccess("asdf|") + self.assertAccess("asdf|") + self.assertAccess("[|") + self.assertAccess("[|]") + self.assertAccess("[abc|") + self.assertAccess("asdf|") class TestCurrentString(LineTestCase): @@ -209,16 +229,16 @@ def setUp(self): self.func = current_object def test_simple(self): - self.assertAccess('.attr1|') - self.assertAccess('.|') - self.assertAccess('Object|') - self.assertAccess('Object|.') - self.assertAccess('.|') - self.assertAccess('.attr2|') - self.assertAccess('.att|r1.attr2') - self.assertAccess('stuff[stuff] + {123: 456} + .attr2|') - self.assertAccess('stuff[asd|fg]') - self.assertAccess('stuff[asdf[asd|fg]') + self.assertAccess(".attr1|") + self.assertAccess(".|") + self.assertAccess("Object|") + self.assertAccess("Object|.") + self.assertAccess(".|") + self.assertAccess(".attr2|") + self.assertAccess(".att|r1.attr2") + self.assertAccess("stuff[stuff] + {123: 456} + .attr2|") + self.assertAccess("stuff[asd|fg]") + self.assertAccess("stuff[asdf[asd|fg]") class TestCurrentAttribute(LineTestCase): @@ -226,14 +246,14 @@ def setUp(self): self.func = current_object_attribute def test_simple(self): - self.assertAccess('Object.') - self.assertAccess('Object.attr1.') - self.assertAccess('Object..attr2') - self.assertAccess('stuff[stuff] + {123: 456} + Object.attr1.') - self.assertAccess('stuff[asd|fg]') - self.assertAccess('stuff[asdf[asd|fg]') - self.assertAccess('Object.attr1.<|attr2>') - self.assertAccess('Object..attr2') + self.assertAccess("Object.") + self.assertAccess("Object.attr1.") + self.assertAccess("Object..attr2") + self.assertAccess("stuff[stuff] + {123: 456} + Object.attr1.") + self.assertAccess("stuff[asd|fg]") + self.assertAccess("stuff[asdf[asd|fg]") + self.assertAccess("Object.attr1.<|attr2>") + self.assertAccess("Object..attr2") class TestCurrentFromImportFrom(LineTestCase): @@ -241,16 +261,16 @@ def setUp(self): self.func = current_from_import_from def test_simple(self): - self.assertAccess('from import path') - self.assertAccess('from import path|') - self.assertAccess('if True|: from sys import path') - self.assertAccess('if True: |from sys import path') - self.assertAccess('if True: from import p|ath') - self.assertAccess('if True: from sys imp|ort path') - self.assertAccess('if True: from sys import |path') - self.assertAccess('if True: from sys import path.stu|ff') - self.assertAccess('if True: from import sep|') - self.assertAccess('from ') + self.assertAccess("from import path") + self.assertAccess("from import path|") + self.assertAccess("if True|: from sys import path") + self.assertAccess("if True: |from sys import path") + self.assertAccess("if True: from import p|ath") + self.assertAccess("if True: from sys imp|ort path") + self.assertAccess("if True: from sys import |path") + self.assertAccess("if True: from sys import path.stu|ff") + self.assertAccess("if True: from import sep|") + self.assertAccess("from ") class TestCurrentFromImportImport(LineTestCase): @@ -258,15 +278,15 @@ def setUp(self): self.func = current_from_import_import def test_simple(self): - self.assertAccess('from sys import ') - self.assertAccess('from sys import ') - self.assertAccess('from sys import |path') - self.assertAccess('from sys| import path') - self.assertAccess('from s|ys import path') - self.assertAccess('from |sys import path') - self.assertAccess('from xml.dom import ') + self.assertAccess("from sys import ") + self.assertAccess("from sys import ") + self.assertAccess("from sys import |path") + self.assertAccess("from sys| import path") + self.assertAccess("from s|ys import path") + self.assertAccess("from |sys import path") + self.assertAccess("from xml.dom import ") # because syntax error - self.assertAccess('from xml.dom import Node.as|d') + self.assertAccess("from xml.dom import Node.as|d") class TestCurrentImport(LineTestCase): @@ -274,15 +294,15 @@ def setUp(self): self.func = current_import def test_simple(self): - self.assertAccess('import ') - self.assertAccess('import ') - self.assertAccess('import |path') - self.assertAccess('import path, ') - self.assertAccess('import path another|') - self.assertAccess('if True: import ') - self.assertAccess('if True: import ') - self.assertAccess('if True: import ') - self.assertAccess('if True: import as something') + self.assertAccess("import ") + self.assertAccess("import ") + self.assertAccess("import |path") + self.assertAccess("import path, ") + self.assertAccess("import path another|") + self.assertAccess("if True: import ") + self.assertAccess("if True: import ") + self.assertAccess("if True: import ") + self.assertAccess("if True: import as something") class TestMethodDefinitionName(LineTestCase): @@ -290,9 +310,9 @@ def setUp(self): self.func = current_method_definition_name def test_simple(self): - self.assertAccess('def ') - self.assertAccess(' def bar(x, y)|:') - self.assertAccess(' def (x, y)') + self.assertAccess("def ") + self.assertAccess(" def bar(x, y)|:") + self.assertAccess(" def (x, y)") class TestSingleWord(LineTestCase): @@ -300,9 +320,9 @@ def setUp(self): self.func = current_single_word def test_simple(self): - self.assertAccess('foo.bar|') - self.assertAccess('.foo|') - self.assertAccess(' ') + self.assertAccess("foo.bar|") + self.assertAccess(".foo|") + self.assertAccess(" ") class TestCurrentExpressionAttribute(LineTestCase): @@ -310,34 +330,34 @@ def setUp(self): self.func = current_expression_attribute def test_simple(self): - self.assertAccess('Object..') - self.assertAccess('Object.<|attr1>.') - self.assertAccess('Object.(|)') - self.assertAccess('Object.another.(|)') - self.assertAccess('asdf asdf asdf.(abc|)') + self.assertAccess("Object..") + self.assertAccess("Object.<|attr1>.") + self.assertAccess("Object.(|)") + self.assertAccess("Object.another.(|)") + self.assertAccess("asdf asdf asdf.(abc|)") def test_without_dot(self): - self.assertAccess('Object|') - self.assertAccess('Object|.') - self.assertAccess('|Object.') + self.assertAccess("Object|") + self.assertAccess("Object|.") + self.assertAccess("|Object.") def test_with_whitespace(self): - self.assertAccess('Object. ') - self.assertAccess('Object .') - self.assertAccess('Object . ') - self.assertAccess('Object .asdf attr|') - self.assertAccess('Object . attr') - self.assertAccess('Object. asdf attr|') - self.assertAccess('Object. attr') - self.assertAccess('Object . asdf attr|') - self.assertAccess('Object . attr') + self.assertAccess("Object. ") + self.assertAccess("Object .") + self.assertAccess("Object . ") + self.assertAccess("Object .asdf attr|") + self.assertAccess("Object . attr") + self.assertAccess("Object. asdf attr|") + self.assertAccess("Object. attr") + self.assertAccess("Object . asdf attr|") + self.assertAccess("Object . attr") def test_indexing(self): - self.assertAccess('abc[def].') - self.assertAccess('abc[def].<|ghi>') - self.assertAccess('abc[def].') - self.assertAccess('abc[def].gh |i') - self.assertAccess('abc[def]|') + self.assertAccess("abc[def].") + self.assertAccess("abc[def].<|ghi>") + self.assertAccess("abc[def].") + self.assertAccess("abc[def].gh |i") + self.assertAccess("abc[def]|") def test_strings(self): self.assertAccess('"hey".') @@ -353,11 +373,12 @@ def setUp(self): self.func = current_dotted_attribute def test_simple(self): - self.assertAccess('|') - self.assertAccess('(|') - self.assertAccess('[|') - self.assertAccess('m.body[0].value|') - self.assertAccess('m.body[0].attr.value|') + self.assertAccess("|") + self.assertAccess("(|") + self.assertAccess("[|") + self.assertAccess("m.body[0].value|") + self.assertAccess("m.body[0].attr.value|") + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 705558d9a..aedfddfe2 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -1,11 +1,23 @@ # encoding: utf-8 -from bpython.curtsiesfrontend.manual_readline import \ - left_arrow, right_arrow, beginning_of_line, forward_word, back_word, \ - end_of_line, delete, last_word_pos, backspace, delete_from_cursor_back, \ - delete_from_cursor_forward, delete_rest_of_word, delete_word_to_cursor, \ - transpose_character_before_cursor, UnconfiguredEdits, \ - delete_word_from_cursor_back +from bpython.curtsiesfrontend.manual_readline import ( + left_arrow, + right_arrow, + beginning_of_line, + forward_word, + back_word, + end_of_line, + delete, + last_word_pos, + backspace, + delete_from_cursor_back, + delete_from_cursor_forward, + delete_rest_of_word, + delete_word_to_cursor, + transpose_character_before_cursor, + UnconfiguredEdits, + delete_word_from_cursor_back, +) from bpython.test import unittest @@ -24,7 +36,7 @@ def test_left_arrow_at_zero(self): def test_left_arrow_at_non_zero(self): for i in range(1, len(self._line)): - expected = (i-1, self._line) + expected = (i - 1, self._line) result = left_arrow(i, self._line) self.assertEqual(expected, result) @@ -108,8 +120,8 @@ def test_back_word(self): line = "going to here from_here" start_pos = 14 prev_word_pos = 9 - self.assertEqual(line[start_pos], 'f') - self.assertEqual(line[prev_word_pos], 'h') + self.assertEqual(line[start_pos], "f") + self.assertEqual(line[prev_word_pos], "h") expected = (prev_word_pos, line) result = back_word(start_pos, line) self.assertEqual(expected, result) @@ -145,31 +157,37 @@ def test_delete_from_cursor_forward(self): expected = (pos, "everything after ") result = delete_from_cursor_forward(line.find("this"), line)[:-1] self.assertEqual(expected, result) - self.assertEqual(delete_from_cursor_forward(0, ''), (0, '', '')) + self.assertEqual(delete_from_cursor_forward(0, ""), (0, "", "")) def test_delete_rest_of_word(self): - self.try_stages_kill([ - 'z|s;df asdf d s;a;a', - 'z|;df asdf d s;a;a', - 'z| asdf d s;a;a', - 'z| d s;a;a', - 'z| s;a;a', - 'z|;a;a', - 'z|;a', - 'z|', - 'z|'], - delete_rest_of_word) + self.try_stages_kill( + [ + "z|s;df asdf d s;a;a", + "z|;df asdf d s;a;a", + "z| asdf d s;a;a", + "z| d s;a;a", + "z| s;a;a", + "z|;a;a", + "z|;a", + "z|", + "z|", + ], + delete_rest_of_word, + ) def test_delete_word_to_cursor(self): - self.try_stages_kill([ - ' a;d sdf ;a;s;d; fjksald|a', - ' a;d sdf ;a;s;d; |a', - ' a;d sdf |a', - ' a;d |a', - ' |a', - '|a', - '|a'], - delete_word_to_cursor) + self.try_stages_kill( + [ + " a;d sdf ;a;s;d; fjksald|a", + " a;d sdf ;a;s;d; |a", + " a;d sdf |a", + " a;d |a", + " |a", + "|a", + "|a", + ], + delete_word_to_cursor, + ) def test_yank_prev_killed_text(self): pass @@ -178,139 +196,149 @@ def test_yank_prev_prev_killed_text(self): pass def try_stages(self, strings, func): - if not all('|' in s for s in strings): + if not all("|" in s for s in strings): raise ValueError("Need to use '|' to specify cursor") - stages = [(s.index('|'), s.replace('|', '')) for s in strings] - for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], - stages[1:]): + stages = [(s.index("|"), s.replace("|", "")) for s in strings] + for (initial_pos, initial), (final_pos, final) in zip( + stages[:-1], stages[1:] + ): self.assertEqual(func(initial_pos, initial), (final_pos, final)) def try_stages_kill(self, strings, func): - if not all('|' in s for s in strings): + if not all("|" in s for s in strings): raise ValueError("Need to use '|' to specify cursor") - stages = [(s.index('|'), s.replace('|', '')) for s in strings] - for (initial_pos, initial), (final_pos, final) in zip(stages[:-1], - stages[1:]): - self.assertEqual(func(initial_pos, initial)[:-1], - (final_pos, final)) + stages = [(s.index("|"), s.replace("|", "")) for s in strings] + for (initial_pos, initial), (final_pos, final) in zip( + stages[:-1], stages[1:] + ): + self.assertEqual( + func(initial_pos, initial)[:-1], (final_pos, final) + ) def test_transpose_character_before_cursor(self): - self.try_stages(["as|df asdf", - "ads|f asdf", - "adfs| asdf", - "adf s|asdf", - "adf as|sdf"], transpose_character_before_cursor) + self.try_stages( + [ + "as|df asdf", + "ads|f asdf", + "adfs| asdf", + "adf s|asdf", + "adf as|sdf", + ], + transpose_character_before_cursor, + ) def test_transpose_empty_line(self): - self.assertEqual(transpose_character_before_cursor(0, ''), - (0, '')) + self.assertEqual(transpose_character_before_cursor(0, ""), (0, "")) def test_transpose_first_character(self): - self.assertEqual(transpose_character_before_cursor(0, 'a'), - (0, 'a')) - self.assertEqual(transpose_character_before_cursor(0, 'as'), - (0, 'as')) + self.assertEqual(transpose_character_before_cursor(0, "a"), (0, "a")) + self.assertEqual(transpose_character_before_cursor(0, "as"), (0, "as")) def test_transpose_end_of_line(self): - self.assertEqual(transpose_character_before_cursor(1, 'a'), - (1, 'a')) - self.assertEqual(transpose_character_before_cursor(2, 'as'), - (2, 'sa')) + self.assertEqual(transpose_character_before_cursor(1, "a"), (1, "a")) + self.assertEqual(transpose_character_before_cursor(2, "as"), (2, "sa")) def test_transpose_word_before_cursor(self): pass def test_backspace(self): - self.assertEqual(backspace(2, 'as'), (1, 'a')) - self.assertEqual(backspace(3, 'as '), (2, 'as')) + self.assertEqual(backspace(2, "as"), (1, "a")) + self.assertEqual(backspace(3, "as "), (2, "as")) def test_delete_word_from_cursor_back(self): - self.try_stages_kill([ - "asd;fljk asd;lfjas;dlkfj asdlk jasdf;ljk|", - "asd;fljk asd;lfjas;dlkfj asdlk jasdf;|", - "asd;fljk asd;lfjas;dlkfj asdlk |", - "asd;fljk asd;lfjas;dlkfj |", - "asd;fljk asd;lfjas;|", - "asd;fljk asd;|", - "asd;fljk |", - "asd;|", - "|", - "|"], - delete_word_from_cursor_back) - - self.try_stages_kill([ - " (( asdf |", - " (( |", - "|"], - delete_word_from_cursor_back) + self.try_stages_kill( + [ + "asd;fljk asd;lfjas;dlkfj asdlk jasdf;ljk|", + "asd;fljk asd;lfjas;dlkfj asdlk jasdf;|", + "asd;fljk asd;lfjas;dlkfj asdlk |", + "asd;fljk asd;lfjas;dlkfj |", + "asd;fljk asd;lfjas;|", + "asd;fljk asd;|", + "asd;fljk |", + "asd;|", + "|", + "|", + ], + delete_word_from_cursor_back, + ) + + self.try_stages_kill( + [" (( asdf |", " (( |", "|"], delete_word_from_cursor_back + ) class TestEdits(unittest.TestCase): - def setUp(self): self.edits = UnconfiguredEdits() def test_seq(self): def f(cursor_offset, line): - return ('hi', 2) - self.edits.add('a', f) - self.assertIn('a', self.edits) - self.assertEqual(self.edits['a'], f) - self.assertEqual(self.edits.call('a', cursor_offset=3, line='hello'), - ('hi', 2)) + return ("hi", 2) + + self.edits.add("a", f) + self.assertIn("a", self.edits) + self.assertEqual(self.edits["a"], f) + self.assertEqual( + self.edits.call("a", cursor_offset=3, line="hello"), ("hi", 2) + ) with self.assertRaises(KeyError): - self.edits['b'] + self.edits["b"] with self.assertRaises(KeyError): - self.edits.call('b') + self.edits.call("b") def test_functions_with_bad_signatures(self): def f(something): return (1, 2) + with self.assertRaises(TypeError): - self.edits.add('a', f) + self.edits.add("a", f) def g(cursor_offset, line, something, something_else): return (1, 2) + with self.assertRaises(TypeError): - self.edits.add('a', g) + self.edits.add("a", g) def test_functions_with_bad_return_values(self): def f(cursor_offset, line): - return ('hi',) + return ("hi",) + with self.assertRaises(ValueError): - self.edits.add('a', f) + self.edits.add("a", f) def g(cursor_offset, line): - return ('hi', 1, 2, 3) + return ("hi", 1, 2, 3) + with self.assertRaises(ValueError): - self.edits.add('b', g) + self.edits.add("b", g) def test_config(self): def f(cursor_offset, line): - return ('hi', 2) + return ("hi", 2) def g(cursor_offset, line): - return ('hey', 3) - self.edits.add_config_attr('att', f) - self.assertNotIn('att', self.edits) + return ("hey", 3) + + self.edits.add_config_attr("att", f) + self.assertNotIn("att", self.edits) class config(object): - att = 'c' + att = "c" - key_dispatch = {'c': 'c'} + key_dispatch = {"c": "c"} configured_edits = self.edits.mapping_with_config(config, key_dispatch) - self.assertTrue(configured_edits.__contains__, 'c') - self.assertNotIn('c', self.edits) + self.assertTrue(configured_edits.__contains__, "c") + self.assertNotIn("c", self.edits) with self.assertRaises(NotImplementedError): - configured_edits.add_config_attr('att2', g) + configured_edits.add_config_attr("att2", g) with self.assertRaises(NotImplementedError): - configured_edits.add('d', g) - self.assertEqual(configured_edits.call('c', cursor_offset=5, - line='asfd'), - ('hi', 2)) + configured_edits.add("d", g) + self.assertEqual( + configured_edits.call("c", cursor_offset=5, line="asfd"), ("hi", 2) + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index 0ef21f098..7fb782989 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -17,38 +17,47 @@ def get_fodder_source(test_name): - pattern = r'#StartTest-%s\n(.*?)#EndTest' % (test_name,) - orig, xformed = [re.search(pattern, inspect.getsource(module), re.DOTALL) - for module in [original, processed]] + pattern = r"#StartTest-%s\n(.*?)#EndTest" % (test_name,) + orig, xformed = [ + re.search(pattern, inspect.getsource(module), re.DOTALL) + for module in [original, processed] + ] if not orig: - raise ValueError("Can't locate test %s in original fodder file" % - (test_name,)) + raise ValueError( + "Can't locate test %s in original fodder file" % (test_name,) + ) if not xformed: - raise ValueError("Can't locate test %s in processed fodder file" % - (test_name,)) + raise ValueError( + "Can't locate test %s in processed fodder file" % (test_name,) + ) return orig.group(1), xformed.group(1) class TestPreprocessing(unittest.TestCase): - def assertCompiles(self, source): finished, parsable = code_finished_will_parse(source, compiler) return finished and parsable def test_indent_empty_lines_nops(self): - self.assertEqual(preproc('hello'), 'hello') - self.assertEqual(preproc('hello\ngoodbye'), 'hello\ngoodbye') - self.assertEqual(preproc('a\n b\nc\n'), 'a\n b\nc\n') + self.assertEqual(preproc("hello"), "hello") + self.assertEqual(preproc("hello\ngoodbye"), "hello\ngoodbye") + self.assertEqual(preproc("a\n b\nc\n"), "a\n b\nc\n") def assertShowWhitespaceEqual(self, a, b): self.assertEqual( - a, b, - ''.join(difflib.context_diff(a.replace(' ', '~').splitlines(True), - b.replace(' ', '~').splitlines(True), - fromfile='actual', - tofile='expected', - n=5))) + a, + b, + "".join( + difflib.context_diff( + a.replace(" ", "~").splitlines(True), + b.replace(" ", "~").splitlines(True), + fromfile="actual", + tofile="expected", + n=5, + ) + ), + ) def assertDefinitionIndented(self, obj): name = obj.__name__ @@ -76,20 +85,24 @@ def test_empty_line_within_class(self): self.assertIndented(original.BlankLineInFunction) def test_blank_lines_in_for_loop(self): - self.assertIndented('blank_lines_in_for_loop') + self.assertIndented("blank_lines_in_for_loop") - @skip("More advanced technique required: need to try compiling and " - "backtracking") + @skip( + "More advanced technique required: need to try compiling and " + "backtracking" + ) def test_blank_line_in_try_catch(self): - self.assertIndented('blank_line_in_try_catch') + self.assertIndented("blank_line_in_try_catch") - @skip("More advanced technique required: need to try compiling and " - "backtracking") + @skip( + "More advanced technique required: need to try compiling and " + "backtracking" + ) def test_blank_line_in_try_catch_else(self): - self.assertIndented('blank_line_in_try_catch_else') + self.assertIndented("blank_line_in_try_catch_else") def test_blank_trailing_line(self): - self.assertIndented('blank_trailing_line') + self.assertIndented("blank_trailing_line") def test_tabs(self): self.assertIndented(original.tabs) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 532ee5865..d76dadc60 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -16,19 +16,18 @@ from bpython.test import unittest, TEST_CONFIG -pypy = 'PyPy' in sys.version +pypy = "PyPy" in sys.version def setup_config(conf): config_struct = config.Struct() config.loadini(config_struct, TEST_CONFIG) - if conf is not None and 'autocomplete_mode' in conf: - config_struct.autocomplete_mode = conf['autocomplete_mode'] + if conf is not None and "autocomplete_mode" in conf: + config_struct.autocomplete_mode = conf["autocomplete_mode"] return config_struct class FakeHistory(repl.History): - def __init__(self): pass @@ -45,19 +44,18 @@ def __init__(self, conf=None): class FakeCliRepl(cli.CLIRepl, FakeRepl): def __init__(self): - self.s = '' + self.s = "" self.cpos = 0 self.rl_history = FakeHistory() class TestMatchesIterator(unittest.TestCase): - def setUp(self): - self.matches = ['bobby', 'bobbies', 'bobberina'] + self.matches = ["bobby", "bobbies", "bobberina"] self.matches_iterator = repl.MatchesIterator() - self.matches_iterator.current_word = 'bob' - self.matches_iterator.orig_line = 'bob' - self.matches_iterator.orig_cursor_offset = len('bob') + self.matches_iterator.current_word = "bob" + self.matches_iterator.orig_line = "bob" + self.matches_iterator.orig_cursor_offset = len("bob") self.matches_iterator.matches = self.matches def test_next(self): @@ -102,10 +100,10 @@ def test_update(self): slice = islice(self.matches_iterator, 0, 3) self.assertEqual(list(slice), self.matches) - newmatches = ['string', 'str', 'set'] + newmatches = ["string", "str", "set"] completer = mock.Mock() - completer.locate.return_value = (0, 1, 's') - self.matches_iterator.update(1, 's', newmatches, completer) + completer.locate.return_value = (0, 1, "s") + self.matches_iterator.update(1, "s", newmatches, completer) newslice = islice(newmatches, 0, 3) self.assertNotEqual(list(slice), self.matches) @@ -116,15 +114,18 @@ def test_cur_line(self): completer.locate.return_value = ( 0, self.matches_iterator.orig_cursor_offset, - self.matches_iterator.orig_line) + self.matches_iterator.orig_line, + ) self.matches_iterator.completer = completer with self.assertRaises(ValueError): self.matches_iterator.cur_line() self.assertEqual(next(self.matches_iterator), self.matches[0]) - self.assertEqual(self.matches_iterator.cur_line(), - (len(self.matches[0]), self.matches[0])) + self.assertEqual( + self.matches_iterator.cur_line(), + (len(self.matches[0]), self.matches[0]), + ) def test_is_cseq(self): self.assertTrue(self.matches_iterator.is_cseq()) @@ -157,17 +158,21 @@ def set_input_line(self, line): self.repl.cursor_offset = len(line) def test_func_name(self): - for (line, expected_name) in [("spam(", "spam"), - ("spam(map([]", "map"), - ("spam((), ", "spam")]: + for (line, expected_name) in [ + ("spam(", "spam"), + ("spam(map([]", "map"), + ("spam((), ", "spam"), + ]: self.set_input_line(line) self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.current_func.__name__, expected_name) def test_func_name_method_issue_479(self): - for (line, expected_name) in [("o.spam(", "spam"), - ("o.spam(map([]", "map"), - ("o.spam((), ", "spam")]: + for (line, expected_name) in [ + ("o.spam(", "spam"), + ("o.spam(map([]", "map"), + ("o.spam((), ", "spam"), + ]: self.set_input_line(line) self.assertTrue(self.repl.get_args()) self.assertEqual(self.repl.current_func.__name__, expected_name) @@ -224,7 +229,7 @@ def test_issue572(self): self.set_input_line("WonderfulSpam(") self.assertTrue(self.repl.get_args()) - @unittest.skipIf(pypy, 'pypy pydoc doesn\'t have this') + @unittest.skipIf(pypy, "pypy pydoc doesn't have this") def test_issue583(self): self.repl = FakeRepl() self.repl.push("a = 1.2\n", False) @@ -245,22 +250,22 @@ def test_function_expressions(self): te = self.assertTupleEqual fa = lambda line: repl.Repl._funcname_and_argnum(line) for line, (func, argnum) in [ - ('spam(', ('spam', 0)), - ('spam((), ', ('spam', 1)), - ('spam.eggs((), ', ('spam.eggs', 1)), - ('spam[abc].eggs((), ', ('spam[abc].eggs', 1)), - ('spam[0].eggs((), ', ('spam[0].eggs', 1)), - ('spam[a + b]eggs((), ', ('spam[a + b]eggs', 1)), - ('spam().eggs((), ', ('spam().eggs', 1)), - ('spam(1, 2).eggs((), ', ('spam(1, 2).eggs', 1)), - ('spam(1, f(1)).eggs((), ', ('spam(1, f(1)).eggs', 1)), - ('[0].eggs((), ', ('[0].eggs', 1)), - ('[0][0]((), {}).eggs((), ', ('[0][0]((), {}).eggs', 1)), - ('a + spam[0].eggs((), ', ('spam[0].eggs', 1)), + ("spam(", ("spam", 0)), + ("spam((), ", ("spam", 1)), + ("spam.eggs((), ", ("spam.eggs", 1)), + ("spam[abc].eggs((), ", ("spam[abc].eggs", 1)), + ("spam[0].eggs((), ", ("spam[0].eggs", 1)), + ("spam[a + b]eggs((), ", ("spam[a + b]eggs", 1)), + ("spam().eggs((), ", ("spam().eggs", 1)), + ("spam(1, 2).eggs((), ", ("spam(1, 2).eggs", 1)), + ("spam(1, f(1)).eggs((), ", ("spam(1, f(1)).eggs", 1)), + ("[0].eggs((), ", ("[0].eggs", 1)), + ("[0][0]((), {}).eggs((), ", ("[0][0]((), {}).eggs", 1)), + ("a + spam[0].eggs((), ", ("spam[0].eggs", 1)), ("spam(", ("spam", 0)), ("spam(map([]", ("map", 0)), - ("spam((), ", ("spam", 1)) - ]: + ("spam((), ", ("spam", 1)), + ]: te(fa(line), (func, argnum)) @@ -285,29 +290,35 @@ def assert_get_source_error_for_current_function(self, func, msg): self.fail("Should have raised SourceNotFound") def test_current_function(self): - self.set_input_line('INPUTLINE') + self.set_input_line("INPUTLINE") self.repl.current_func = inspect.getsource - self.assertIn("text of the source code", - self.repl.get_source_of_current_name()) + self.assertIn( + "text of the source code", self.repl.get_source_of_current_name() + ) self.assert_get_source_error_for_current_function( - [], "No source code found for INPUTLINE") + [], "No source code found for INPUTLINE" + ) self.assert_get_source_error_for_current_function( - list.pop, "No source code found for INPUTLINE") + list.pop, "No source code found for INPUTLINE" + ) - @unittest.skipIf(pypy, 'different errors for PyPy') + @unittest.skipIf(pypy, "different errors for PyPy") def test_current_function_cpython(self): - self.set_input_line('INPUTLINE') + self.set_input_line("INPUTLINE") self.assert_get_source_error_for_current_function( - collections.defaultdict.copy, "No source code found for INPUTLINE") + collections.defaultdict.copy, "No source code found for INPUTLINE" + ) self.assert_get_source_error_for_current_function( - collections.defaultdict, "could not find class definition") + collections.defaultdict, "could not find class definition" + ) def test_current_line(self): - self.repl.interp.locals['a'] = socket.socket - self.set_input_line('a') - self.assertIn('dup(self)', self.repl.get_source_of_current_name()) + self.repl.interp.locals["a"] = socket.socket + self.set_input_line("a") + self.assertIn("dup(self)", self.repl.get_source_of_current_name()) + # TODO add tests for various failures without using current function @@ -317,12 +328,12 @@ def setUp(self): self.repl = FakeRepl() self.repl.interact.confirm = lambda msg: True self.repl.interact.notify = lambda msg: None - self.repl.config.editor = 'true' + self.repl.config.editor = "true" def test_create_config(self): tmp_dir = tempfile.mkdtemp() try: - config_path = os.path.join(tmp_dir, 'newdir', 'config') + config_path = os.path.join(tmp_dir, "newdir", "config") self.repl.config.config_path = config_path self.repl.edit_config() self.assertTrue(os.path.exists(config_path)) @@ -332,7 +343,6 @@ def test_create_config(self): class TestRepl(unittest.TestCase): - def set_input_line(self, line): """Set current input line of the test REPL.""" self.repl.current_line = line @@ -348,48 +358,52 @@ def test_current_string(self): self.assertEqual(self.repl.current_string(), '"2"') self.set_input_line('a = "2" + 2') - self.assertEqual(self.repl.current_string(), '') + self.assertEqual(self.repl.current_string(), "") def test_push(self): self.repl = FakeRepl() self.repl.push("foobar = 2") - self.assertEqual(self.repl.interp.locals['foobar'], 2) + self.assertEqual(self.repl.interp.locals["foobar"], 2) # COMPLETE TESTS # 1. Global tests def test_simple_global_complete(self): - self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) + self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE}) self.set_input_line("d") self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) - self.assertEqual(self.repl.matches_iter.matches, - ['def', 'del', 'delattr(', 'dict(', 'dir(', - 'divmod(']) + self.assertTrue(hasattr(self.repl.matches_iter, "matches")) + self.assertEqual( + self.repl.matches_iter.matches, + ["def", "del", "delattr(", "dict(", "dir(", "divmod("], + ) def test_substring_global_complete(self): - self.repl = FakeRepl({'autocomplete_mode': autocomplete.SUBSTRING}) + self.repl = FakeRepl({"autocomplete_mode": autocomplete.SUBSTRING}) self.set_input_line("time") self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) - self.assertEqual(self.repl.matches_iter.matches, - ['RuntimeError(', 'RuntimeWarning(']) + self.assertTrue(hasattr(self.repl.matches_iter, "matches")) + self.assertEqual( + self.repl.matches_iter.matches, ["RuntimeError(", "RuntimeWarning("] + ) def test_fuzzy_global_complete(self): - self.repl = FakeRepl({'autocomplete_mode': autocomplete.FUZZY}) + self.repl = FakeRepl({"autocomplete_mode": autocomplete.FUZZY}) self.set_input_line("doc") self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) - self.assertEqual(self.repl.matches_iter.matches, - ['UnboundLocalError(', '__doc__'] if not py3 else - ['ChildProcessError(', 'UnboundLocalError(', - '__doc__']) + self.assertTrue(hasattr(self.repl.matches_iter, "matches")) + self.assertEqual( + self.repl.matches_iter.matches, + ["UnboundLocalError(", "__doc__"] + if not py3 + else ["ChildProcessError(", "UnboundLocalError(", "__doc__"], + ) # 2. Attribute tests def test_simple_attribute_complete(self): - self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) + self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE}) self.set_input_line("Foo.b") code = "class Foo():\n\tdef bar(self):\n\t\tpass\n" @@ -397,11 +411,11 @@ def test_simple_attribute_complete(self): self.repl.push(line) self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) - self.assertEqual(self.repl.matches_iter.matches, ['Foo.bar']) + self.assertTrue(hasattr(self.repl.matches_iter, "matches")) + self.assertEqual(self.repl.matches_iter.matches, ["Foo.bar"]) def test_substring_attribute_complete(self): - self.repl = FakeRepl({'autocomplete_mode': autocomplete.SUBSTRING}) + self.repl = FakeRepl({"autocomplete_mode": autocomplete.SUBSTRING}) self.set_input_line("Foo.az") code = "class Foo():\n\tdef baz(self):\n\t\tpass\n" @@ -409,11 +423,11 @@ def test_substring_attribute_complete(self): self.repl.push(line) self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) - self.assertEqual(self.repl.matches_iter.matches, ['Foo.baz']) + self.assertTrue(hasattr(self.repl.matches_iter, "matches")) + self.assertEqual(self.repl.matches_iter.matches, ["Foo.baz"]) def test_fuzzy_attribute_complete(self): - self.repl = FakeRepl({'autocomplete_mode': autocomplete.FUZZY}) + self.repl = FakeRepl({"autocomplete_mode": autocomplete.FUZZY}) self.set_input_line("Foo.br") code = "class Foo():\n\tdef bar(self):\n\t\tpass\n" @@ -421,29 +435,29 @@ def test_fuzzy_attribute_complete(self): self.repl.push(line) self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) - self.assertEqual(self.repl.matches_iter.matches, ['Foo.bar']) + self.assertTrue(hasattr(self.repl.matches_iter, "matches")) + self.assertEqual(self.repl.matches_iter.matches, ["Foo.bar"]) # 3. Edge cases def test_updating_namespace_complete(self): - self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) + self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE}) self.set_input_line("foo") self.repl.push("foobar = 2") self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) - self.assertEqual(self.repl.matches_iter.matches, ['foobar']) + self.assertTrue(hasattr(self.repl.matches_iter, "matches")) + self.assertEqual(self.repl.matches_iter.matches, ["foobar"]) def test_file_should_not_appear_in_complete(self): - self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) + self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE}) self.set_input_line("_") self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) - self.assertNotIn('__file__', self.repl.matches_iter.matches) + self.assertTrue(hasattr(self.repl.matches_iter, "matches")) + self.assertNotIn("__file__", self.repl.matches_iter.matches) # 4. Parameter names def test_paremeter_name_completion(self): - self.repl = FakeRepl({'autocomplete_mode': autocomplete.SIMPLE}) + self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE}) self.set_input_line("foo(ab") code = "def foo(abc=1, abd=2, xyz=3):\n\tpass\n" @@ -451,13 +465,13 @@ def test_paremeter_name_completion(self): self.repl.push(line) self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter, 'matches')) - self.assertEqual(self.repl.matches_iter.matches, - ['abc=', 'abd=', 'abs(']) + self.assertTrue(hasattr(self.repl.matches_iter, "matches")) + self.assertEqual( + self.repl.matches_iter.matches, ["abc=", "abd=", "abs("] + ) class TestCliRepl(unittest.TestCase): - def setUp(self): self.repl = FakeCliRepl() @@ -478,12 +492,11 @@ def test_addstr(self): self.assertEqual(self.repl.s, "foobar") self.repl.cpos = 3 - self.repl.addstr('buzz') + self.repl.addstr("buzz") self.assertEqual(self.repl.s, "foobuzzbar") class TestCliReplTab(unittest.TestCase): - def setUp(self): self.repl = FakeCliRepl() @@ -594,5 +607,5 @@ def test_fuzzy_expand(self): pass -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 92107c060..9065f75cc 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -3,11 +3,13 @@ import ast import numbers -from bpython.simpleeval import (simple_eval, - evaluate_current_expression, - EvaluationError, - safe_get_attribute, - safe_get_attribute_new_style) +from bpython.simpleeval import ( + simple_eval, + evaluate_current_expression, + EvaluationError, + safe_get_attribute, + safe_get_attribute_new_style, +) from bpython.test import unittest from bpython._py3compat import py3 @@ -23,46 +25,46 @@ def test_matches_stdlib(self): def test_indexing(self): """Literals can be indexed into""" - self.assertEqual(simple_eval('[1,2][0]'), 1) - self.assertEqual(simple_eval('a', {'a': 1}), 1) + self.assertEqual(simple_eval("[1,2][0]"), 1) + self.assertEqual(simple_eval("a", {"a": 1}), 1) def test_name_lookup(self): """Names can be lookup up in a namespace""" - self.assertEqual(simple_eval('a', {'a': 1}), 1) - self.assertEqual(simple_eval('map'), map) - self.assertEqual(simple_eval('a[b]', {'a': {'c': 1}, 'b': 'c'}), 1) + self.assertEqual(simple_eval("a", {"a": 1}), 1) + self.assertEqual(simple_eval("map"), map) + self.assertEqual(simple_eval("a[b]", {"a": {"c": 1}, "b": "c"}), 1) def test_allow_name_lookup(self): """Names can be lookup up in a namespace""" - self.assertEqual(simple_eval('a', {'a': 1}), 1) + self.assertEqual(simple_eval("a", {"a": 1}), 1) def test_lookup_on_suspicious_types(self): class FakeDict(object): pass with self.assertRaises(ValueError): - simple_eval('a[1]', {'a': FakeDict()}) + simple_eval("a[1]", {"a": FakeDict()}) class TrickyDict(dict): def __getitem__(self, index): self.fail("doing key lookup isn't safe") with self.assertRaises(ValueError): - simple_eval('a[1]', {'a': TrickyDict()}) + simple_eval("a[1]", {"a": TrickyDict()}) class SchrodingersDict(dict): def __getattribute__(inner_self, attr): self.fail("doing attribute lookup might have side effects") with self.assertRaises(ValueError): - simple_eval('a[1]', {'a': SchrodingersDict()}) + simple_eval("a[1]", {"a": SchrodingersDict()}) class SchrodingersCatsDict(dict): def __getattr__(inner_self, attr): self.fail("doing attribute lookup might have side effects") with self.assertRaises(ValueError): - simple_eval('a[1]', {'a': SchrodingersDict()}) + simple_eval("a[1]", {"a": SchrodingersDict()}) def test_operators_on_suspicious_types(self): class Spam(numbers.Number): @@ -70,113 +72,117 @@ def __add__(inner_self, other): self.fail("doing attribute lookup might have side effects") with self.assertRaises(ValueError): - simple_eval('a + 1', {'a': Spam()}) + simple_eval("a + 1", {"a": Spam()}) def test_operators_on_numbers(self): - self.assertEqual(simple_eval('-2'), -2) - self.assertEqual(simple_eval('1 + 1'), 2) - self.assertEqual(simple_eval('a - 2', {'a': 1}), -1) + self.assertEqual(simple_eval("-2"), -2) + self.assertEqual(simple_eval("1 + 1"), 2) + self.assertEqual(simple_eval("a - 2", {"a": 1}), -1) with self.assertRaises(ValueError): - simple_eval('2 * 3') + simple_eval("2 * 3") with self.assertRaises(ValueError): - simple_eval('2 ** 3') + simple_eval("2 ** 3") def test_function_calls_raise(self): with self.assertRaises(ValueError): - simple_eval('1()') + simple_eval("1()") def test_nonexistant_names_raise(self): with self.assertRaises(EvaluationError): - simple_eval('a') + simple_eval("a") def test_attribute_access(self): class Foo(object): abc = 1 - self.assertEqual(simple_eval('foo.abc', {'foo': Foo()}), 1) + self.assertEqual(simple_eval("foo.abc", {"foo": Foo()}), 1) class TestEvaluateCurrentExpression(unittest.TestCase): - def assertEvaled(self, line, value, ns=None): - assert line.count('|') == 1 - cursor_offset = line.find('|') - line = line.replace('|', '') - self.assertEqual(evaluate_current_expression(cursor_offset, line, ns), - value) + assert line.count("|") == 1 + cursor_offset = line.find("|") + line = line.replace("|", "") + self.assertEqual( + evaluate_current_expression(cursor_offset, line, ns), value + ) def assertCannotEval(self, line, ns=None): - assert line.count('|') == 1 - cursor_offset = line.find('|') - line = line.replace('|', '') + assert line.count("|") == 1 + cursor_offset = line.find("|") + line = line.replace("|", "") with self.assertRaises(EvaluationError): evaluate_current_expression(cursor_offset, line, ns) def test_simple(self): - self.assertEvaled('[1].a|bc', [1]) - self.assertEvaled('[1].abc|', [1]) - self.assertEvaled('[1].|abc', [1]) - self.assertEvaled('[1]. |abc', [1]) - self.assertEvaled('[1] .|abc', [1]) - self.assertCannotEval('[1].abc |', [1]) - self.assertCannotEval('[1]. abc |', [1]) - self.assertCannotEval('[2][1].a|bc', [1]) + self.assertEvaled("[1].a|bc", [1]) + self.assertEvaled("[1].abc|", [1]) + self.assertEvaled("[1].|abc", [1]) + self.assertEvaled("[1]. |abc", [1]) + self.assertEvaled("[1] .|abc", [1]) + self.assertCannotEval("[1].abc |", [1]) + self.assertCannotEval("[1]. abc |", [1]) + self.assertCannotEval("[2][1].a|bc", [1]) def test_nonsense(self): - self.assertEvaled('!@#$ [1].a|bc', [1]) - self.assertEvaled('--- [2][0].a|bc', 2) + self.assertEvaled("!@#$ [1].a|bc", [1]) + self.assertEvaled("--- [2][0].a|bc", 2) self.assertCannotEval('"asdf".centered()[1].a|bc') - self.assertEvaled('"asdf"[1].a|bc', 's') + self.assertEvaled('"asdf"[1].a|bc', "s") def test_with_namespace(self): - self.assertEvaled('a[1].a|bc', 'd', {'a': 'adsf'}) - self.assertCannotEval('a[1].a|bc', {}) + self.assertEvaled("a[1].a|bc", "d", {"a": "adsf"}) + self.assertCannotEval("a[1].a|bc", {}) class A(object): - a = 'a' + a = "a" class B(A): - b = 'b' + b = "b" class Property(object): @property def prop(self): - raise AssertionError('Property __get__ executed') + raise AssertionError("Property __get__ executed") class Slots(object): - __slots__ = ['s1', 's2', 's3'] + __slots__ = ["s1", "s2", "s3"] if not py3: + @property def s3(self): - raise AssertionError('Property __get__ executed') + raise AssertionError("Property __get__ executed") class SlotsSubclass(Slots): @property def s4(self): - raise AssertionError('Property __get__ executed') + raise AssertionError("Property __get__ executed") class OverriddenGetattr(object): def __getattr__(self, attr): - raise AssertionError('custom __getattr__ executed') + raise AssertionError("custom __getattr__ executed") + a = 1 class OverriddenGetattribute(object): def __getattribute__(self, attr): - raise AssertionError('custom __getattribute__ executed') + raise AssertionError("custom __getattribute__ executed") + a = 1 class OverriddenMRO(object): def __mro__(self): - raise AssertionError('custom mro executed') + raise AssertionError("custom mro executed") + a = 1 @@ -184,69 +190,70 @@ def __mro__(self): class TestSafeGetAttribute(unittest.TestCase): - def test_lookup_on_object(self): a = A() a.x = 1 - self.assertEqual(safe_get_attribute_new_style(a, 'x'), 1) - self.assertEqual(safe_get_attribute_new_style(a, 'a'), 'a') + self.assertEqual(safe_get_attribute_new_style(a, "x"), 1) + self.assertEqual(safe_get_attribute_new_style(a, "a"), "a") b = B() b.y = 2 - self.assertEqual(safe_get_attribute_new_style(b, 'y'), 2) - self.assertEqual(safe_get_attribute_new_style(b, 'a'), 'a') - self.assertEqual(safe_get_attribute_new_style(b, 'b'), 'b') + self.assertEqual(safe_get_attribute_new_style(b, "y"), 2) + self.assertEqual(safe_get_attribute_new_style(b, "a"), "a") + self.assertEqual(safe_get_attribute_new_style(b, "b"), "b") def test_avoid_running_properties(self): p = Property() - self.assertEqual(safe_get_attribute_new_style(p, 'prop'), - Property.prop) + self.assertEqual(safe_get_attribute_new_style(p, "prop"), Property.prop) - @unittest.skipIf(py3, 'Old-style classes not in Python 3') + @unittest.skipIf(py3, "Old-style classes not in Python 3") def test_raises_on_old_style_class(self): class Old: pass + with self.assertRaises(ValueError): - safe_get_attribute_new_style(Old, 'asdf') + safe_get_attribute_new_style(Old, "asdf") def test_lookup_with_slots(self): s = Slots() - s.s1 = 's1' - self.assertEqual(safe_get_attribute(s, 's1'), 's1') - self.assertIsInstance(safe_get_attribute_new_style(s, 's1'), - member_descriptor) + s.s1 = "s1" + self.assertEqual(safe_get_attribute(s, "s1"), "s1") + self.assertIsInstance( + safe_get_attribute_new_style(s, "s1"), member_descriptor + ) with self.assertRaises(AttributeError): - safe_get_attribute(s, 's2') - self.assertIsInstance(safe_get_attribute_new_style(s, 's2'), - member_descriptor) + safe_get_attribute(s, "s2") + self.assertIsInstance( + safe_get_attribute_new_style(s, "s2"), member_descriptor + ) def test_lookup_on_slots_classes(self): sga = safe_get_attribute s = SlotsSubclass() - self.assertIsInstance(sga(Slots, 's1'), member_descriptor) - self.assertIsInstance(sga(SlotsSubclass, 's1'), member_descriptor) - self.assertIsInstance(sga(SlotsSubclass, 's4'), property) - self.assertIsInstance(sga(s, 's4'), property) + self.assertIsInstance(sga(Slots, "s1"), member_descriptor) + self.assertIsInstance(sga(SlotsSubclass, "s1"), member_descriptor) + self.assertIsInstance(sga(SlotsSubclass, "s4"), property) + self.assertIsInstance(sga(s, "s4"), property) @unittest.skipIf(py3, "Py 3 doesn't allow slots and prop in same class") def test_lookup_with_property_and_slots(self): sga = safe_get_attribute s = SlotsSubclass() - self.assertIsInstance(sga(Slots, 's3'), property) - self.assertEqual(safe_get_attribute(s, 's3'), - Slots.__dict__['s3']) - self.assertIsInstance(sga(SlotsSubclass, 's3'), property) + self.assertIsInstance(sga(Slots, "s3"), property) + self.assertEqual(safe_get_attribute(s, "s3"), Slots.__dict__["s3"]) + self.assertIsInstance(sga(SlotsSubclass, "s3"), property) def test_lookup_on_overridden_methods(self): sga = safe_get_attribute - self.assertEqual(sga(OverriddenGetattr(), 'a'), 1) - self.assertEqual(sga(OverriddenGetattribute(), 'a'), 1) - self.assertEqual(sga(OverriddenMRO(), 'a'), 1) + self.assertEqual(sga(OverriddenGetattr(), "a"), 1) + self.assertEqual(sga(OverriddenGetattribute(), "a"), 1) + self.assertEqual(sga(OverriddenMRO(), "a"), 1) with self.assertRaises(AttributeError): - sga(OverriddenGetattr(), 'b') + sga(OverriddenGetattr(), "b") with self.assertRaises(AttributeError): - sga(OverriddenGetattribute(), 'b') + sga(OverriddenGetattribute(), "b") with self.assertRaises(AttributeError): - sga(OverriddenMRO(), 'b') + sga(OverriddenMRO(), "b") + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index 4b3b2f956..2c45ac5ce 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -13,12 +13,16 @@ translator = None if py3: + def _(message): return translator.gettext(message) def ngettext(singular, plural, n): return translator.ngettext(singular, plural, n) + + else: + def _(message): return translator.ugettext(message) @@ -28,17 +32,20 @@ def ngettext(singular, plural, n): def init(locale_dir=None, languages=None): try: - locale.setlocale(locale.LC_ALL, '') + locale.setlocale(locale.LC_ALL, "") except locale.Error: # This means that the user's environment is broken. Let's just continue # with the default C locale. - sys.stderr.write("Error: Your locale settings are not supported by " - "the system. Using the fallback 'C' locale instead. " - "Please fix your locale settings.\n") + sys.stderr.write( + "Error: Your locale settings are not supported by " + "the system. Using the fallback 'C' locale instead. " + "Please fix your locale settings.\n" + ) global translator if locale_dir is None: - locale_dir = os.path.join(package_dir, 'translations') + locale_dir = os.path.join(package_dir, "translations") - translator = gettext.translation('bpython', locale_dir, languages, - fallback=True) + translator = gettext.translation( + "bpython", locale_dir, languages, fallback=True + ) diff --git a/bpython/urwid.py b/bpython/urwid.py index a4e215219..4fa2f7fe3 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -72,15 +72,15 @@ # blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default COLORMAP = { - 'k': 'black', - 'r': 'dark red', # or light red? - 'g': 'dark green', # or light green? - 'y': 'yellow', - 'b': 'dark blue', # or light blue? - 'm': 'dark magenta', # or light magenta? - 'c': 'dark cyan', # or light cyan? - 'w': 'white', - 'd': 'default', + "k": "black", + "r": "dark red", # or light red? + "g": "dark green", # or light green? + "y": "yellow", + "b": "dark blue", # or light blue? + "m": "dark magenta", # or light magenta? + "c": "dark cyan", # or light cyan? + "w": "white", + "d": "default", } @@ -93,7 +93,7 @@ class EvalProtocol(basic.LineOnlyReceiver): - delimiter = '\n' + delimiter = "\n" def __init__(self, myrepl): self.repl = myrepl @@ -102,10 +102,9 @@ def lineReceived(self, line): # HACK! # TODO: deal with encoding issues here... self.repl.main_loop.process_input(line) - self.repl.main_loop.process_input(['enter']) + self.repl.main_loop.process_input(["enter"]) class EvalFactory(protocol.ServerFactory): - def __init__(self, myrepl): self.repl = myrepl @@ -116,7 +115,8 @@ def buildProtocol(self, addr): # If Twisted is not available urwid has no TwistedEventLoop attribute. # Code below will try to import reactor before using TwistedEventLoop. # I assume TwistedEventLoop will be available if that import succeeds. -if urwid.VERSION < (1, 0, 0) and hasattr(urwid, 'TwistedEventLoop'): +if urwid.VERSION < (1, 0, 0) and hasattr(urwid, "TwistedEventLoop"): + class TwistedEventLoop(urwid.TwistedEventLoop): """TwistedEventLoop modified to properly stop the reactor. @@ -144,9 +144,12 @@ def wrapper(*args, **kwargs): print(sys.exc_info()) self._exc_info = sys.exc_info() self.reactor.crash() + return wrapper + + else: - TwistedEventLoop = getattr(urwid, 'TwistedEventLoop', None) + TwistedEventLoop = getattr(urwid, "TwistedEventLoop", None) class StatusbarEdit(urwid.Edit): @@ -155,7 +158,7 @@ class StatusbarEdit(urwid.Edit): This class only adds a single signal that is emitted if the user presses Enter.""" - signals = urwid.Edit.signals + ['prompt_enter'] + signals = urwid.Edit.signals + ["prompt_enter"] def __init__(self, *args, **kwargs): self.single = False @@ -163,14 +166,14 @@ def __init__(self, *args, **kwargs): def keypress(self, size, key): if self.single: - urwid.emit_signal(self, 'prompt_enter', self, key) - elif key == 'enter': - urwid.emit_signal(self, 'prompt_enter', self, self.get_edit_text()) + urwid.emit_signal(self, "prompt_enter", self, key) + elif key == "enter": + urwid.emit_signal(self, "prompt_enter", self, self.get_edit_text()) else: return urwid.Edit.keypress(self, size, key) -urwid.register_signal(StatusbarEdit, 'prompt_enter') +urwid.register_signal(StatusbarEdit, "prompt_enter") class Statusbar(object): @@ -193,20 +196,20 @@ class Statusbar(object): The "widget" attribute is an urwid widget. """ - signals = ['prompt_result'] + signals = ["prompt_result"] def __init__(self, config, s=None, main_loop=None): self.config = config self.timer = None self.main_loop = main_loop - self.s = s or '' + self.s = s or "" - self.text = urwid.Text(('main', self.s)) + self.text = urwid.Text(("main", self.s)) # use wrap mode 'clip' to just cut off at the end of line - self.text.set_wrap_mode('clip') + self.text.set_wrap_mode("clip") - self.edit = StatusbarEdit(('main', '')) - urwid.connect_signal(self.edit, 'prompt_enter', self._on_prompt_enter) + self.edit = StatusbarEdit(("main", "")) + urwid.connect_signal(self.edit, "prompt_enter", self._on_prompt_enter) self.widget = urwid.Columns([self.text, self.edit]) @@ -237,8 +240,8 @@ def prompt(self, s=None, single=False): self._reset_timer() self.edit.single = single - self.edit.set_caption(('main', s or '?')) - self.edit.set_edit_text('') + self.edit.set_caption(("main", s or "?")) + self.edit.set_edit_text("") # hide the text and display the edit widget if not self.edit in self.widget.widget_list: self.widget.widget_list.append(self.edit) @@ -259,22 +262,22 @@ def settext(self, s, permanent=False): if not self.text in self.widget.widget_list: self.widget.widget_list.append(self.text) - self.text.set_text(('main', s)) + self.text.set_text(("main", s)) if permanent: self.s = s def clear(self): """Clear the status bar.""" - self.settext('') + self.settext("") def _on_prompt_enter(self, edit, new_text): """Reset the statusbar and pass the input from the prompt to the caller via 'prompt_result'.""" self.settext(self.s) - urwid.emit_signal(self, 'prompt_result', new_text) + urwid.emit_signal(self, "prompt_result", new_text) -urwid.register_signal(Statusbar, 'prompt_result') +urwid.register_signal(Statusbar, "prompt_result") def decoding_input_filter(keys, raw): @@ -292,7 +295,7 @@ def decoding_input_filter(keys, raw): def format_tokens(tokensource): for token, text in tokensource: - if text == '\n': + if text == "\n": continue # TODO: something about inversing Parenthesis @@ -325,10 +328,10 @@ class BPythonEdit(urwid.Edit): - an "edit-pos-changed" signal is emitted when edit_pos changes. """ - signals = ['edit-pos-changed'] + signals = ["edit-pos-changed"] def __init__(self, config, *args, **kwargs): - self._bpy_text = '' + self._bpy_text = "" self._bpy_attr = [] self._bpy_selectable = True self._bpy_may_move_cursor = False @@ -363,7 +366,7 @@ def set_edit_markup(self, markup): self._bpy_text, self._bpy_attr = urwid.decompose_tagmarkup(markup) else: # decompose_tagmarkup in some urwids fails on the empty list - self._bpy_text, self._bpy_attr = '', [] + self._bpy_text, self._bpy_attr = "", [] # This is redundant when we're called off the 'change' signal. # I'm assuming this is cheap, making that ok. self._invalidate() @@ -391,7 +394,7 @@ def render(self, size, focus=False): def get_pref_col(self, size): # Need to make this deal with us being nonselectable if not self._bpy_selectable: - return 'left' + return "left" return urwid.Edit.get_pref_col(self, size) def move_cursor_to_coords(self, *args): @@ -400,26 +403,26 @@ def move_cursor_to_coords(self, *args): return False def keypress(self, size, key): - if urwid.command_map[key] in ['cursor up', 'cursor down']: + if urwid.command_map[key] in ["cursor up", "cursor down"]: # Do not handle up/down arrow, leave them for the repl. return key self._bpy_may_move_cursor = True try: - if urwid.command_map[key] == 'cursor max left': + if urwid.command_map[key] == "cursor max left": self.edit_pos = 0 - elif urwid.command_map[key] == 'cursor max right': + elif urwid.command_map[key] == "cursor max right": self.edit_pos = len(self.get_edit_text()) - elif urwid.command_map[key] == 'clear word': + elif urwid.command_map[key] == "clear word": # ^w if self.edit_pos == 0: return line = self.get_edit_text() # delete any space left of the cursor - p = len(line[:self.edit_pos].strip()) - line = line[:p] + line[self.edit_pos:] + p = len(line[: self.edit_pos].strip()) + line = line[:p] + line[self.edit_pos :] # delete a full word - np = line.rfind(' ', 0, p) + np = line.rfind(" ", 0, p) if np == -1: line = line[p:] np = 0 @@ -427,15 +430,15 @@ def keypress(self, size, key): line = line[:np] + line[p:] self.set_edit_text(line) self.edit_pos = np - elif urwid.command_map[key] == 'clear line': + elif urwid.command_map[key] == "clear line": line = self.get_edit_text() - self.set_edit_text(line[self.edit_pos:]) + self.set_edit_text(line[self.edit_pos :]) self.edit_pos = 0 - elif key == 'backspace': + elif key == "backspace": line = self.get_edit_text() cpos = len(line) - self.edit_pos if not (cpos or len(line) % self.tab_length or line.strip()): - self.set_edit_text(line[:-self.tab_length]) + self.set_edit_text(line[: -self.tab_length]) else: return urwid.Edit.keypress(self, size, key) else: @@ -494,11 +497,10 @@ def keypress(self, size, key): def mouse_event(self, size, event, button, col, row, focus): # TODO: pass to top widget if visible and inside it. - if not hasattr(self.bottom_w, 'mouse_event'): + if not hasattr(self.bottom_w, "mouse_event"): return False - return self.bottom_w.mouse_event( - size, event, button, col, row, focus) + return self.bottom_w.mouse_event(size, event, button, col, row, focus) def get_cursor_coords(self, size): return self.bottom_w.get_cursor_coords(size) @@ -527,7 +529,7 @@ def render(self, size, focus=False): # - It is a loop. # (ideally it would check how much free space there is, # instead of repeatedly trying smaller sizes) - while 'bottom' in self.listbox.ends_visible((maxcol - 2, rows - 3)): + while "bottom" in self.listbox.ends_visible((maxcol - 2, rows - 3)): rows -= 1 # If we're displaying above the cursor move the top edge down: @@ -536,8 +538,7 @@ def render(self, size, focus=False): # Render *both* windows focused. This is probably not normal in urwid, # but it works nicely. - top_c = self.top_w.render((maxcol, rows), - focus and self.tooltip_focus) + top_c = self.top_w.render((maxcol, rows), focus and self.tooltip_focus) combi_c = urwid.CanvasOverlay(top_c, bottom_c, 0, y) # Use the cursor coordinates from the bottom canvas. @@ -550,14 +551,14 @@ class URWIDInteraction(repl.Interaction): def __init__(self, config, statusbar, frame): repl.Interaction.__init__(self, config, statusbar) self.frame = frame - urwid.connect_signal(statusbar, 'prompt_result', self._prompt_result) + urwid.connect_signal(statusbar, "prompt_result", self._prompt_result) self.callback = None def confirm(self, q, callback): """Ask for yes or no and call callback to return the result""" def callback_wrapper(result): - callback(result.lower() in (_('y'), _('yes'))) + callback(result.lower() in (_("y"), _("yes"))) self.prompt(q, callback_wrapper, single=True) @@ -570,14 +571,14 @@ def prompt(self, s, callback=None, single=False): callback can already start a new prompt.""" if self.callback is not None: - raise Exception('Prompt already in progress') + raise Exception("Prompt already in progress") self.callback = callback self.statusbar.prompt(s, single=single) - self.frame.set_focus('footer') + self.frame.set_focus("footer") def _prompt_result(self, text): - self.frame.set_focus('body') + self.frame.set_focus("body") if self.callback is not None: # The callback might want to start another prompt, so reset it # before calling the callback. @@ -588,7 +589,7 @@ def _prompt_result(self, text): class URWIDRepl(repl.Repl): - _time_between_redraws = .05 # seconds + _time_between_redraws = 0.05 # seconds def __init__(self, event_loop, palette, interpreter, config): repl.Repl.__init__(self, interpreter, config) @@ -602,30 +603,45 @@ def __init__(self, event_loop, palette, interpreter, config): self.tooltip = urwid.ListBox(urwid.SimpleListWalker([])) self.tooltip.grid = None self.overlay = Tooltip(self.listbox, self.tooltip) - self.stdout_hist = '' # native str (bytes in Py2, unicode in Py3) + self.stdout_hist = "" # native str (bytes in Py2, unicode in Py3) self.frame = urwid.Frame(self.overlay) - if urwid.get_encoding_mode() == 'narrow': + if urwid.get_encoding_mode() == "narrow": input_filter = decoding_input_filter else: input_filter = None # This constructs a raw_display.Screen, which nabs sys.stdin/out. self.main_loop = urwid.MainLoop( - self.frame, palette, - event_loop=event_loop, unhandled_input=self.handle_input, - input_filter=input_filter, handle_mouse=False) + self.frame, + palette, + event_loop=event_loop, + unhandled_input=self.handle_input, + input_filter=input_filter, + handle_mouse=False, + ) # String is straight from bpython.cli - self.statusbar = Statusbar(config, - _(" <%s> Rewind <%s> Save <%s> Pastebin " - " <%s> Pager <%s> Show Source ") % - (config.undo_key, config.save_key, config.pastebin_key, - config.last_output_key, config.show_source_key), self.main_loop) + self.statusbar = Statusbar( + config, + _( + " <%s> Rewind <%s> Save <%s> Pastebin " + " <%s> Pager <%s> Show Source " + ) + % ( + config.undo_key, + config.save_key, + config.pastebin_key, + config.last_output_key, + config.show_source_key, + ), + self.main_loop, + ) self.frame.set_footer(self.statusbar.widget) self.interact = URWIDInteraction( - self.config, self.statusbar, self.frame) + self.config, self.statusbar, self.frame + ) self.edits = [] self.edit = None @@ -639,10 +655,10 @@ def __init__(self, event_loop, palette, interpreter, config): # Subclasses of Repl need to implement echo, current_line, cw def echo(self, orig_s): - s = orig_s.rstrip('\n') + s = orig_s.rstrip("\n") if s: if self.current_output is None: - self.current_output = urwid.Text(('output', s)) + self.current_output = urwid.Text(("output", s)) if self.edit is None: self.listbox.body.append(self.current_output) # Focus the widget we just added to force the @@ -660,8 +676,9 @@ def echo(self, orig_s): else: # XXX this assumes this all has "output" markup applied. self.current_output.set_text( - ('output', self.current_output.text + s)) - if orig_s.endswith('\n'): + ("output", self.current_output.text + s) + ) + if orig_s.endswith("\n"): self.current_output = None # If we hit this repeatedly in a loop the redraw is rather @@ -684,7 +701,8 @@ def maybe_redraw(loop, self): self._redraw_handle = None self._redraw_handle = self.main_loop.set_alarm_in( - self._time_between_redraws, maybe_redraw, self) + self._time_between_redraws, maybe_redraw, self + ) self._redraw_time = time.time() else: self._redraw_pending = True @@ -697,13 +715,18 @@ def maybe_redraw(loop, self): def _get_current_line(self): if self.edit is None: - return '' + return "" return self.edit.get_edit_text() def _set_current_line(self, line): self.edit.set_edit_text(line) - current_line = property(_get_current_line, _set_current_line, None, - "Return the current line (the one the cursor is in).") + + current_line = property( + _get_current_line, + _set_current_line, + None, + "Return the current line (the one the cursor is in).", + ) def cw(self): """Return the current word (incomplete word left of cursor).""" @@ -717,13 +740,12 @@ def cw(self): return # Stolen from cli. TODO: clean up and split out. - if (not text or - (not text[-1].isalnum() and text[-1] not in ('.', '_'))): + if not text or (not text[-1].isalnum() and text[-1] not in (".", "_")): return # Seek backwards in text for the first non-identifier char: for i, c in enumerate(reversed(text)): - if not c.isalnum() and c not in ('.', '_'): + if not c.isalnum() and c not in (".", "_"): break else: # No non-identifiers, return everything. @@ -742,8 +764,13 @@ def _get_cursor_offset(self): def _set_cursor_offset(self, offset): self.edit.edit_pos = offset - cursor_offset = property(_get_cursor_offset, _set_cursor_offset, None, - "The cursor offset from the beginning of the line") + + cursor_offset = property( + _get_cursor_offset, + _set_cursor_offset, + None, + "The cursor offset from the beginning of the line", + ) def _populate_completion(self): widget_list = self.tooltip.body @@ -758,11 +785,12 @@ def _populate_completion(self): args, varargs, varkw, defaults = args[:4] if py3: kwonly = self.funcprops.argspec.kwonly - kwonly_defaults = self.funcprops.argspec.kwonly_defaults or {} + kwonly_defaults = ( + self.funcprops.argspec.kwonly_defaults or {} + ) else: kwonly, kwonly_defaults = [], {} - markup = [('bold name', func_name), - ('name', ': (')] + markup = [("bold name", func_name), ("name", ": (")] # the isinstance checks if we're in a positional arg # (instead of a keyword arg), I think @@ -777,13 +805,13 @@ def _populate_completion(self): else: kw = None - if not k and str(i) == 'self': - color = 'name' + if not k and str(i) == "self": + color = "name" else: - color = 'token' + color = "token" if k == in_arg or i == in_arg: - color = 'bold ' + color + color = "bold " + color if not py3: # See issue #138: We need to format tuple unpacking correctly @@ -793,46 +821,51 @@ def _populate_completion(self): else: markup.append((color, str(i))) if kw is not None: - markup.extend([('punctuation', '='), - ('token', kw)]) + markup.extend([("punctuation", "="), ("token", kw)]) if k != len(args) - 1: - markup.append(('punctuation', ', ')) + markup.append(("punctuation", ", ")) if varargs: if args: - markup.append(('punctuation', ', ')) - markup.append(('token', '*' + varargs)) + markup.append(("punctuation", ", ")) + markup.append(("token", "*" + varargs)) if kwonly: if not varargs: if args: - markup.append(('punctuation', ', ')) - markup.append(('punctuation', '*')) + markup.append(("punctuation", ", ")) + markup.append(("punctuation", "*")) for arg in kwonly: if arg == in_arg: - color = 'bold token' + color = "bold token" else: - color = 'token' - markup.extend([('punctuation', ', '), - (color, arg)]) + color = "token" + markup.extend([("punctuation", ", "), (color, arg)]) if arg in kwonly_defaults: - markup.extend([('punctuation', '='), - ('token', repr(kwonly_defaults[arg]))]) + markup.extend( + [ + ("punctuation", "="), + ("token", repr(kwonly_defaults[arg])), + ] + ) if varkw: if args or varargs or kwonly: - markup.append(('punctuation', ', ')) - markup.append(('token', '**' + varkw)) - markup.append(('punctuation', ')')) + markup.append(("punctuation", ", ")) + markup.append(("token", "**" + varkw)) + markup.append(("punctuation", ")")) widget_list.append(urwid.Text(markup)) if self.matches_iter.matches: attr_map = {} - focus_map = {'main': 'operator'} - texts = [urwid.AttrMap(urwid.Text(('main', match)), - attr_map, focus_map) - for match in self.matches_iter.matches] + focus_map = {"main": "operator"} + texts = [ + urwid.AttrMap( + urwid.Text(("main", match)), attr_map, focus_map + ) + for match in self.matches_iter.matches + ] width = max(text.original_widget.pack()[0] for text in texts) - gridflow = urwid.GridFlow(texts, width, 1, 0, 'left') + gridflow = urwid.GridFlow(texts, width, 1, 0, "left") widget_list.append(gridflow) self.tooltip.grid = gridflow self.overlay.tooltip_focus = False @@ -846,7 +879,7 @@ def _populate_completion(self): if self.docstring: # TODO: use self.format_docstring? needs a width/height... docstring = self.docstring - widget_list.append(urwid.Text(('comment', docstring))) + widget_list.append(urwid.Text(("comment", docstring))) def reprint_line(self, lineno, tokens): edit = self.edits[-len(self.buffer) + lineno - 1] @@ -856,7 +889,7 @@ def getstdout(self): """This method returns the 'spoofed' stdout buffer, for writing to a file or sending to a pastebin or whatever.""" - return self.stdout_hist + '\n' + return self.stdout_hist + "\n" def ask_confirmation(self, q): """Ask for yes or no and return boolean""" @@ -865,14 +898,14 @@ def ask_confirmation(self, q): except ValueError: return False - return reply.lower() in ('y', 'yes') + return reply.lower() in ("y", "yes") def reevaluate(self): """Clear the buffer, redraw the screen and re-evaluate the history""" self.evaluating = True - self.stdout_hist = '' - self.f_string = '' + self.stdout_hist = "" + self.f_string = "" self.buffer = [] self.scr.erase() self.s_hist = [] @@ -884,22 +917,23 @@ def reevaluate(self): self.iy, self.ix = self.scr.getyx() for line in self.history: if py3: - self.stdout_hist += line + '\n' + self.stdout_hist += line + "\n" else: - self.stdout_hist += line.encode( - locale.getpreferredencoding()) + '\n' + self.stdout_hist += ( + line.encode(locale.getpreferredencoding()) + "\n" + ) self.print_line(line) self.s_hist[-1] += self.f_string # I decided it was easier to just do this manually # than to make the print_line and history stuff more flexible. - self.scr.addstr('\n') + self.scr.addstr("\n") more = self.push(line) self.prompt(more) self.iy, self.ix = self.scr.getyx() self.cpos = 0 indent = repl.next_indentation(self.s, self.config.tab_length) - self.s = '' + self.s = "" self.scr.refresh() if self.buffer: @@ -907,17 +941,17 @@ def reevaluate(self): self.tab() self.evaluating = False - #map(self.push, self.history) + # map(self.push, self.history) # ^-- That's how simple this method was at first :( def write(self, s): """For overriding stdout defaults""" - if '\x04' in s: - for block in s.split('\x04'): + if "\x04" in s: + for block in s.split("\x04"): self.write(block) return - if s.rstrip() and '\x03' in s: - t = s.split('\x03')[1] + if s.rstrip() and "\x03" in s: + t = s.split("\x03")[1] else: t = s @@ -965,12 +999,12 @@ def keyboard_interrupt(self): self.edit.make_readonly() self.edit = None self.buffer = [] - self.echo('KeyboardInterrupt') + self.echo("KeyboardInterrupt") self.prompt(False) else: # I do not quite remember if this is reachable, but let's # be safe. - self.echo('KeyboardInterrupt') + self.echo("KeyboardInterrupt") def prompt(self, more): # Clear current output here, or output resulting from the @@ -985,24 +1019,25 @@ def prompt(self, more): # input to be the same type, using ascii as encoding. If the # caption is bytes this breaks typing non-ascii into bpython. if not more: - caption = ('prompt', self.ps1) + caption = ("prompt", self.ps1) if py3: self.stdout_hist += self.ps1 else: self.stdout_hist += self.ps1.encode(getpreferredencoding()) else: - caption = ('prompt_more', self.ps2) + caption = ("prompt_more", self.ps2) if py3: self.stdout_hist += self.ps2 else: self.stdout_hist += self.ps2.encode(getpreferredencoding()) self.edit = BPythonEdit(self.config, caption=caption) - urwid.connect_signal(self.edit, 'change', self.on_input_change) - urwid.connect_signal(self.edit, 'edit-pos-changed', - self.on_edit_pos_changed) + urwid.connect_signal(self.edit, "change", self.on_input_change) + urwid.connect_signal( + self.edit, "edit-pos-changed", self.on_edit_pos_changed + ) # Do this after connecting the change signal handler: - self.edit.insert_text(4 * self.next_indentation() * ' ') + self.edit.insert_text(4 * self.next_indentation() * " ") self.edits.append(self.edit) self.listbox.body.append(self.edit) self.listbox.set_focus(len(self.listbox.body) - 1) @@ -1019,7 +1054,8 @@ def on_input_change(self, edit, text): # If we call this synchronously the get_edit_text() in repl.cw # still returns the old text... self.main_loop.set_alarm_in( - 0, lambda *args: self._populate_completion()) + 0, lambda *args: self._populate_completion() + ) def on_edit_pos_changed(self, edit, position): """Gets called when the cursor position inside the edit changed. @@ -1031,10 +1067,10 @@ def on_edit_pos_changed(self, edit, position): def handle_input(self, event): # Since most of the input handling here should be handled in the edit # instead, we return here early if the edit doesn't have the focus. - if self.frame.get_focus() != 'body': + if self.frame.get_focus() != "body": return - if event == 'enter': + if event == "enter": inp = self.edit.get_edit_text() self.history.append(inp) self.edit.make_readonly() @@ -1043,32 +1079,32 @@ def handle_input(self, event): self.stdout_hist += inp else: self.stdout_hist += inp.encode(locale.getpreferredencoding()) - self.stdout_hist += '\n' + self.stdout_hist += "\n" self.edit = None # This may take a while, so force a redraw first: self.main_loop.draw_screen() more = self.push(inp) self.prompt(more) - elif event == 'ctrl d': + elif event == "ctrl d": # ctrl+d on an empty line exits, otherwise deletes if self.edit is not None: if not self.edit.get_edit_text(): raise urwid.ExitMainLoop() else: - self.main_loop.process_input(['delete']) - elif urwid.command_map[event] == 'cursor up': + self.main_loop.process_input(["delete"]) + elif urwid.command_map[event] == "cursor up": # "back" from bpython.cli self.rl_history.enter(self.edit.get_edit_text()) - self.edit.set_edit_text('') + self.edit.set_edit_text("") self.edit.insert_text(self.rl_history.back()) - elif urwid.command_map[event] == 'cursor down': + elif urwid.command_map[event] == "cursor down": # "fwd" from bpython.cli self.rl_history.enter(self.edit.get_edit_text()) - self.edit.set_edit_text('') + self.edit.set_edit_text("") self.edit.insert_text(self.rl_history.forward()) - elif urwid.command_map[event] == 'next selectable': + elif urwid.command_map[event] == "next selectable": self.tab() - elif urwid.command_map[event] == 'prev selectable': + elif urwid.command_map[event] == "prev selectable": self.tab(True) # else: # self.echo(repr(event)) @@ -1095,7 +1131,7 @@ def tab(self, back=False): if not num_spaces: num_spaces = self.config.tab_length - self.edit.insert_text(' ' * num_spaces) + self.edit.insert_text(" " * num_spaces) return True if not self.matches_iter: @@ -1128,40 +1164,78 @@ def main(args=None, locals_=None, banner=None): translations.init() # TODO: maybe support displays other than raw_display? - config, options, exec_args = bpargs.parse(args, ( - 'Urwid options', None, [ - Option('--twisted', '-T', action='store_true', - help=_('Run twisted reactor.')), - Option('--reactor', '-r', - help=_('Select specific reactor (see --help-reactors). ' - 'Implies --twisted.')), - Option('--help-reactors', action='store_true', - help=_('List available reactors for -r.')), - Option('--plugin', '-p', - help=_('twistd plugin to run (use twistd for a list). ' - 'Use "--" to pass further options to the plugin.')), - Option('--server', '-s', type='int', - help=_('Port to run an eval server on (forces Twisted).')), - ])) + config, options, exec_args = bpargs.parse( + args, + ( + "Urwid options", + None, + [ + Option( + "--twisted", + "-T", + action="store_true", + help=_("Run twisted reactor."), + ), + Option( + "--reactor", + "-r", + help=_( + "Select specific reactor (see --help-reactors). " + "Implies --twisted." + ), + ), + Option( + "--help-reactors", + action="store_true", + help=_("List available reactors for -r."), + ), + Option( + "--plugin", + "-p", + help=_( + "twistd plugin to run (use twistd for a list). " + 'Use "--" to pass further options to the plugin.' + ), + ), + Option( + "--server", + "-s", + type="int", + help=_("Port to run an eval server on (forces Twisted)."), + ), + ], + ), + ) if options.help_reactors: try: from twisted.application import reactors + # Stolen from twisted.application.app (twistd). for r in reactors.getReactorTypes(): - print(' %-4s\t%s' % (r.shortName, r.description)) + print(" %-4s\t%s" % (r.shortName, r.description)) except ImportError: - sys.stderr.write('No reactors are available. Please install ' - 'twisted for reactor support.\n') + sys.stderr.write( + "No reactors are available. Please install " + "twisted for reactor support.\n" + ) return palette = [ - (name, COLORMAP[color.lower()], 'default', - 'bold' if color.isupper() else 'default') - for name, color in iteritems(config.color_scheme)] - palette.extend([ - ('bold ' + name, color + ',bold', background, monochrome) - for name, color, background, monochrome in palette]) + ( + name, + COLORMAP[color.lower()], + "default", + "bold" if color.isupper() else "default", + ) + for name, color in iteritems(config.color_scheme) + ] + palette.extend( + [ + ("bold " + name, color + ",bold", background, monochrome) + for name, color, background, monochrome in palette + ] + ) if options.server or options.plugin: options.twisted = True @@ -1170,8 +1244,10 @@ def main(args=None, locals_=None, banner=None): try: from twisted.application import reactors except ImportError: - sys.stderr.write('No reactors are available. Please install ' - 'twisted for reactor support.\n') + sys.stderr.write( + "No reactors are available. Please install " + "twisted for reactor support.\n" + ) return try: # XXX why does this not just return the reactor it installed? @@ -1179,16 +1255,17 @@ def main(args=None, locals_=None, banner=None): if reactor is None: from twisted.internet import reactor except reactors.NoSuchReactor: - sys.stderr.write('Reactor %s does not exist\n' % ( - options.reactor,)) + sys.stderr.write("Reactor %s does not exist\n" % (options.reactor,)) return event_loop = TwistedEventLoop(reactor) elif options.twisted: try: from twisted.internet import reactor except ImportError: - sys.stderr.write('No reactors are available. Please install ' - 'twisted for reactor support.\n') + sys.stderr.write( + "No reactors are available. Please install " + "twisted for reactor support.\n" + ) return event_loop = TwistedEventLoop(reactor) else: @@ -1203,20 +1280,22 @@ def main(args=None, locals_=None, banner=None): from twisted import plugin from twisted.application import service except ImportError: - sys.stderr.write('No twisted plugins are available. Please install ' - 'twisted for twisted plugin support.\n') + sys.stderr.write( + "No twisted plugins are available. Please install " + "twisted for twisted plugin support.\n" + ) return for plug in plugin.getPlugins(service.IServiceMaker): if plug.tapname == options.plugin: break else: - sys.stderr.write('Plugin %s does not exist\n' % (options.plugin,)) + sys.stderr.write("Plugin %s does not exist\n" % (options.plugin,)) return plugopts = plug.options() plugopts.parseOptions(exec_args) serv = plug.makeService(plugopts) - extend_locals['service'] = serv + extend_locals["service"] = serv reactor.callWhenRunning(serv.startService) exec_args = [] interpreter = repl.Interpreter(locals_, locale.getpreferredencoding()) @@ -1228,13 +1307,14 @@ def main(args=None, locals_=None, banner=None): if options.server: factory = EvalFactory(myrepl) - reactor.listenTCP(options.server, factory, interface='127.0.0.1') + reactor.listenTCP(options.server, factory, interface="127.0.0.1") if options.reactor: # Twisted sets a sigInt handler that stops the reactor unless # it sees a different custom signal handler. def sigint(*args): reactor.callFromThread(myrepl.keyboard_interrupt) + signal.signal(signal.SIGINT, sigint) # Save stdin, stdout and stderr for later restoration @@ -1278,7 +1358,8 @@ def run_with_screen_before_mainloop(): # up an equivalent to reactor.callFromThread (which # is what our Twisted sigint handler does) myrepl.main_loop.set_alarm_in( - 0, lambda *args: myrepl.keyboard_interrupt()) + 0, lambda *args: myrepl.keyboard_interrupt() + ) continue break @@ -1294,20 +1375,21 @@ def start(main_loop, user_data): if not options.interactive: raise urwid.ExitMainLoop() if not exec_args: - sys.path.insert(0, '') + sys.path.insert(0, "") # this is CLIRepl.startup inlined. - filename = os.environ.get('PYTHONSTARTUP') + filename = os.environ.get("PYTHONSTARTUP") if filename and os.path.isfile(filename): - with open(filename, 'r') as f: + with open(filename, "r") as f: if py3: - interpreter.runsource(f.read(), filename, 'exec') + interpreter.runsource(f.read(), filename, "exec") else: - interpreter.runsource(f.read(), filename, 'exec', - encode=False) + interpreter.runsource( + f.read(), filename, "exec", encode=False + ) if banner is not None: myrepl.write(banner) - myrepl.write('\n') + myrepl.write("\n") myrepl.start() # This bypasses main_loop.set_alarm_in because we must *not* @@ -1328,16 +1410,16 @@ def run_find_coroutine(): def load_urwid_command_map(config): - urwid.command_map[key_dispatch[config.up_one_line_key]] = 'cursor up' - urwid.command_map[key_dispatch[config.down_one_line_key]] = 'cursor down' - urwid.command_map[key_dispatch['C-a']] = 'cursor max left' - urwid.command_map[key_dispatch['C-e']] = 'cursor max right' - urwid.command_map[key_dispatch[config.pastebin_key]] = 'pastebin' - urwid.command_map[key_dispatch['C-f']] = 'cursor right' - urwid.command_map[key_dispatch['C-b']] = 'cursor left' - urwid.command_map[key_dispatch['C-d']] = 'delete' - urwid.command_map[key_dispatch[config.clear_word_key]] = 'clear word' - urwid.command_map[key_dispatch[config.clear_line_key]] = 'clear line' + urwid.command_map[key_dispatch[config.up_one_line_key]] = "cursor up" + urwid.command_map[key_dispatch[config.down_one_line_key]] = "cursor down" + urwid.command_map[key_dispatch["C-a"]] = "cursor max left" + urwid.command_map[key_dispatch["C-e"]] = "cursor max right" + urwid.command_map[key_dispatch[config.pastebin_key]] = "pastebin" + urwid.command_map[key_dispatch["C-f"]] = "cursor right" + urwid.command_map[key_dispatch["C-b"]] = "cursor left" + urwid.command_map[key_dispatch["C-d"]] = "delete" + urwid.command_map[key_dispatch[config.clear_word_key]] = "clear word" + urwid.command_map[key_dispatch[config.clear_line_key]] = "clear line" """ @@ -1354,5 +1436,5 @@ def load_urwid_command_map(config): 'up_one_line': 'C-p', 'yank_from_buffer': 'C-y'}, """ -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..b12d166c9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[tool.black] +line-length = 80 +target_version = ["py27"] +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.eggs + | \.mypy_cache + | \.tox + | venv + | _build + | buck-out + | build + | dist +)/ +''' From 7d514d3c0d2c6725a0424cb0547ac7bcd159a87d Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Fri, 22 Nov 2019 13:10:13 +0000 Subject: [PATCH 0925/1650] Add a pypi shield to the README. --- README.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 8264667c5..317aa450d 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,13 @@ -|ImageLink|_ - -.. |ImageLink| image:: https://travis-ci.org/bpython/bpython.svg?branch=master -.. _ImageLink: https://travis-ci.org/bpython/bpython +.. image:: https://travis-ci.org/bpython/bpython.svg?branch=master + :target: https://travis-ci.org/bpython/bpython .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/ambv/black +.. image:: https://img.shields.io/pypi/v/bpython + :target: https://pypi.org/project/bpython + + *********************************************************************** bpython: A fancy curses interface to the Python interactive interpreter *********************************************************************** From e660dbc3f7f1079f71e85abd1999bb70d6e58437 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Fri, 22 Nov 2019 13:21:04 +0000 Subject: [PATCH 0926/1650] Preliminary read-the-docs badge, domain pending. --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 317aa450d..755216044 100644 --- a/README.rst +++ b/README.rst @@ -7,6 +7,9 @@ .. image:: https://img.shields.io/pypi/v/bpython :target: https://pypi.org/project/bpython +.. image:: https://readthedocs.org/projects/pinnwand/badge/?version=latest + :target: https://pinnwand.readthedocs.io/en/latest/ + *********************************************************************** bpython: A fancy curses interface to the Python interactive interpreter From d52c538fe4eb1973d855340d7a998db38e4731f8 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Fri, 22 Nov 2019 13:22:41 +0000 Subject: [PATCH 0927/1650] Slightly saner ordering of shields. --- README.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 755216044..e2f6583bf 100644 --- a/README.rst +++ b/README.rst @@ -1,15 +1,15 @@ -.. image:: https://travis-ci.org/bpython/bpython.svg?branch=master - :target: https://travis-ci.org/bpython/bpython - -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/ambv/black - .. image:: https://img.shields.io/pypi/v/bpython :target: https://pypi.org/project/bpython +.. image:: https://travis-ci.org/bpython/bpython.svg?branch=master + :target: https://travis-ci.org/bpython/bpython + .. image:: https://readthedocs.org/projects/pinnwand/badge/?version=latest :target: https://pinnwand.readthedocs.io/en/latest/ +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black + *********************************************************************** bpython: A fancy curses interface to the Python interactive interpreter From 128fd2a136c957e116e3e2fa126ca2ff8d4be185 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 22 Nov 2019 14:27:29 +0100 Subject: [PATCH 0928/1650] travis: Test with 3.8 --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2ae9ef4a4..981593f8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ python: - "3.5" - "3.6" - "3.7" - - "3.8-dev" + - "3.8" - "pypy" - "pypy3" @@ -21,7 +21,6 @@ env: matrix: allow_failures: - - python: "3.8-dev" - python: "pypy" - python: "pypy3" From eb2a47c838914056aef8eed2c05d5b8c4499cd97 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 22 Nov 2019 15:19:37 +0100 Subject: [PATCH 0929/1650] travis: Remove 3.4 Reached its EOL in March 2019 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 981593f8c..879817cd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ notifications: python: - "2.7" - - "3.4" - "3.5" - "3.6" - "3.7" From 79bc2f325aa56e7542a7e0a25adbe2a14be98a49 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 22 Nov 2019 20:54:50 +0100 Subject: [PATCH 0930/1650] Ignore test/fodder --- bpython/test/fodder/original.py | 23 ++++++++------------- bpython/test/fodder/processed.py | 35 ++++++++++++++------------------ pyproject.toml | 1 + 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/bpython/test/fodder/original.py b/bpython/test/fodder/original.py index 5a8ab2eb3..1afd64129 100644 --- a/bpython/test/fodder/original.py +++ b/bpython/test/fodder/original.py @@ -1,7 +1,6 @@ # careful: whitespace is very important in this file # also, this code runs - so everything should be a noop - class BlankLineBetweenMethods(object): def method1(self): pass @@ -9,29 +8,27 @@ def method1(self): def method2(self): pass - def BlankLineInFunction(self): return 7 pass - -# StartTest-blank_lines_in_for_loop +#StartTest-blank_lines_in_for_loop for i in range(2): pass pass -# EndTest +#EndTest -# StartTest-blank_line_in_try_catch +#StartTest-blank_line_in_try_catch try: 1 except: 2 -# EndTest +#EndTest -# StartTest-blank_line_in_try_catch_else +#StartTest-blank_line_in_try_catch_else try: 1 @@ -40,15 +37,13 @@ def BlankLineInFunction(self): else: 3 -# EndTest +#EndTest -# StartTest-blank_trailing_line +#StartTest-blank_trailing_line def foo(): return 1 - -# EndTest - +#EndTest def tabs(): - return 1 + return 1 diff --git a/bpython/test/fodder/processed.py b/bpython/test/fodder/processed.py index 1656ffe93..6b6331662 100644 --- a/bpython/test/fodder/processed.py +++ b/bpython/test/fodder/processed.py @@ -1,53 +1,48 @@ -# careful! Whitespace is very important in this file - +#careful! Whitespace is very important in this file class BlankLineBetweenMethods(object): def method1(self): pass - + def method2(self): pass - def BlankLineInFunction(self): return 7 - + pass - -# StartTest-blank_lines_in_for_loop +#StartTest-blank_lines_in_for_loop for i in range(2): pass - + pass -# EndTest +#EndTest -# StartTest-blank_line_in_try_catch +#StartTest-blank_line_in_try_catch try: 1 - + except: 2 -# EndTest +#EndTest -# StartTest-blank_line_in_try_catch_else +#StartTest-blank_line_in_try_catch_else try: 1 - + except: 2 - + else: 3 -# EndTest +#EndTest -# StartTest-blank_trailing_line +#StartTest-blank_trailing_line def foo(): return 1 - -# EndTest - +#EndTest def tabs(): return 1 diff --git a/pyproject.toml b/pyproject.toml index b12d166c9..ad3cc4caf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,5 +14,6 @@ exclude = ''' | buck-out | build | dist + | bpython/test/fodder )/ ''' From 81e7cd8ef40dfe1892519b3df1c64d6c60cdd156 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 22 Nov 2019 21:17:44 +0100 Subject: [PATCH 0931/1650] Fix test with Python 3.8 --- bpython/test/test_interpreter.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 597ae8832..4bdb67551 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -40,7 +40,19 @@ def test_syntaxerror(self): i.runsource("1.1.1.1") - if pypy: + if sys.version_info[:2] >= (3, 8): + expected = ( + " File " + + green('""') + + ", line " + + bold(magenta("1")) + + "\n 1.1.1.1\n ^\n" + + bold(red("SyntaxError")) + + ": " + + cyan("invalid syntax") + + "\n" + ) + elif pypy: expected = ( " File " + green('""') From b772e1b0e608d5b17faebc7c2ec94a9958a90779 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Nov 2019 20:18:41 +0100 Subject: [PATCH 0932/1650] Fix urwid tests under Python 3 --- bpython/test/test_crashers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index d2d5c0f14..9b6382640 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -9,6 +9,7 @@ import textwrap from bpython.test import unittest, TEST_CONFIG +from bpython.config import getpreferredencoding try: from twisted.internet import reactor @@ -57,6 +58,7 @@ def run_bpython(self, input): Returns bpython's output. """ result = Deferred() + encoding = getpreferredencoding() class Protocol(ProcessProtocol): STATES = (SEND_INPUT, COLLECT) = range(2) @@ -68,7 +70,7 @@ def __init__(self): self.state = next(self.states) def outReceived(self, data): - self.data += data + self.data += data.decode(encoding) if self.delayed_call is not None: self.delayed_call.cancel() self.delayed_call = reactor.callLater(0.5, self.next) @@ -79,7 +81,7 @@ def next(self): index = self.data.find(">>> ") if index >= 0: self.data = self.data[index + 4 :] - self.transport.write(input) + self.transport.write(input.encode(encoding)) self.state = next(self.states) else: self.transport.closeStdin() From 779ccf11848f5b0f61a8d1754b571ca251fbfc3b Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Sun, 29 Dec 2019 20:21:27 +1100 Subject: [PATCH 0933/1650] Fix simple typo: atempt -> attempt Closes #785 --- bpython/test/test_autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 535f55cb7..c1bf1edd9 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -179,7 +179,7 @@ def test_formatting_takes_just_last_part(self): class MockNumPy(object): - """This is a mock numpy object that raises an error when there is an atempt + """This is a mock numpy object that raises an error when there is an attempt to convert it to a boolean.""" def __nonzero__(self): From e710adcc2063b1047b3ec9007b1a747dca4ab755 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Sat, 4 Jan 2020 10:34:31 +0000 Subject: [PATCH 0934/1650] Use `not in`. --- .flake8 | 5 +++++ bpython/cli.py | 2 +- bpython/urwid.py | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..c321e71c9 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = E203, E266, E501, W503 +max-line-length = 80 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 diff --git a/bpython/cli.py b/bpython/cli.py index 589f49045..7ab5e2fb3 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1032,7 +1032,7 @@ def p_key(self, key): elif key == "\x03": raise KeyboardInterrupt() - elif key[0:3] == "PAD" and not key in ("PAD0", "PADSTOP"): + elif key[0:3] == "PAD" and key not in ("PAD0", "PADSTOP"): pad_keys = { "PADMINUS": "-", "PADPLUS": "+", diff --git a/bpython/urwid.py b/bpython/urwid.py index 4fa2f7fe3..3b425179b 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -243,7 +243,7 @@ def prompt(self, s=None, single=False): self.edit.set_caption(("main", s or "?")) self.edit.set_edit_text("") # hide the text and display the edit widget - if not self.edit in self.widget.widget_list: + if self.edit not in self.widget.widget_list: self.widget.widget_list.append(self.edit) if self.text in self.widget.widget_list: self.widget.widget_list.remove(self.text) @@ -259,7 +259,7 @@ def settext(self, s, permanent=False): # hide the edit and display the text widget if self.edit in self.widget.widget_list: self.widget.widget_list.remove(self.edit) - if not self.text in self.widget.widget_list: + if self.text not in self.widget.widget_list: self.widget.widget_list.append(self.text) self.text.set_text(("main", s)) From c1f9699a3f2884a0c429aaa93497d6cb91266e58 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 5 Jan 2020 11:42:02 +0100 Subject: [PATCH 0935/1650] Update CHANGELOG --- CHANGELOG | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5e529171e..b73c9a35d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,7 +8,11 @@ New features: Fixes: * #765: Display correct signature for decorted functions. - Thanks to Bendeikt Rascher-Friesenhausen. + Thanks to Benedikt Rascher-Friesenhausen. +* #776: Protect get_args from user code exceptions +* Improve lock file handling on Windows + +Support for Python 3.8 has been added. Support for Python 3.4 has been dropped. 0.18 ---- From 08c490f1d6fa27133432d6c8a1302ad432e7d9c7 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Sun, 5 Jan 2020 10:59:01 +0000 Subject: [PATCH 0936/1650] Show deprecation warnings for urwid/curses/python2. --- CHANGELOG | 7 +++++++ bpython/cli.py | 10 ++++++++++ bpython/curtsies.py | 5 +++++ bpython/urwid.py | 10 ++++++++++ 4 files changed, 32 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 5e529171e..02a2ca307 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,13 @@ Changelog 0.19 ---- +General information: +* The bpython-cli and bpython-urwid rendering backends have been deprecated and + will show a warning that they'll be removed in a future release when started. +* Usage in combination with Python 2 has been deprecated. This does not mean that + support is dropped instantly but rather that at some point in the future we will + stop running our testcases against Python 2. + New features: Fixes: diff --git a/bpython/cli.py b/bpython/cli.py index 7ab5e2fb3..d64729252 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1988,6 +1988,16 @@ def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): if banner is not None: clirepl.write(banner) clirepl.write("\n") + + # XXX these deprecation warnings need to go at some point + clirepl.write("WARNING: You are using `bpython-cli`, the curses backend for `bpython`. This backend has been deprecated in version 0.19 and might disappear in a future version.") + clirepl.write("\n") + + if sys.version_info[0] == 2: + # XXX these deprecation warnings need to go at some point + clirepl.write("WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version.") + clirepl.write("\n") + exit_value = clirepl.repl() if hasattr(sys, "exitfunc"): sys.exitfunc() diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 05a3e91c7..10e718559 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -201,6 +201,11 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): print(bpargs.version_banner()) if banner is not None: print(banner) + + if sys.version_info[0] == 2: + # XXX these deprecation warnings need to go at some point + print("WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version.") + global repl repl = FullCurtsiesRepl(config, locals_, welcome_message, interp) try: diff --git a/bpython/urwid.py b/bpython/urwid.py index 3b425179b..38454d099 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1390,6 +1390,16 @@ def start(main_loop, user_data): if banner is not None: myrepl.write(banner) myrepl.write("\n") + + # XXX these deprecation warnings need to go at some point + myrepl.write("WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. This backend has been deprecated in version 0.19 and might disappear in a future version.") + myrepl.write("\n") + + if sys.version_info[0] == 2: + # XXX these deprecation warnings need to go at some point + myrepl.write("WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version.") + myrepl.write("\n") + myrepl.start() # This bypasses main_loop.set_alarm_in because we must *not* From 552c19b8d8dfde8ba3734fa45ec9e06b69b3f819 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Sun, 5 Jan 2020 11:08:33 +0000 Subject: [PATCH 0937/1650] Blacken. --- bpython/cli.py | 8 ++++++-- bpython/curtsies.py | 4 +++- bpython/urwid.py | 8 ++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index d64729252..ac4ee11a2 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1990,12 +1990,16 @@ def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): clirepl.write("\n") # XXX these deprecation warnings need to go at some point - clirepl.write("WARNING: You are using `bpython-cli`, the curses backend for `bpython`. This backend has been deprecated in version 0.19 and might disappear in a future version.") + clirepl.write( + "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. This backend has been deprecated in version 0.19 and might disappear in a future version." + ) clirepl.write("\n") if sys.version_info[0] == 2: # XXX these deprecation warnings need to go at some point - clirepl.write("WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version.") + clirepl.write( + "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." + ) clirepl.write("\n") exit_value = clirepl.repl() diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 10e718559..d108a2c96 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -204,7 +204,9 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): if sys.version_info[0] == 2: # XXX these deprecation warnings need to go at some point - print("WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version.") + print( + "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." + ) global repl repl = FullCurtsiesRepl(config, locals_, welcome_message, interp) diff --git a/bpython/urwid.py b/bpython/urwid.py index 38454d099..16cf6a605 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1392,12 +1392,16 @@ def start(main_loop, user_data): myrepl.write("\n") # XXX these deprecation warnings need to go at some point - myrepl.write("WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. This backend has been deprecated in version 0.19 and might disappear in a future version.") + myrepl.write( + "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. This backend has been deprecated in version 0.19 and might disappear in a future version." + ) myrepl.write("\n") if sys.version_info[0] == 2: # XXX these deprecation warnings need to go at some point - myrepl.write("WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version.") + myrepl.write( + "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." + ) myrepl.write("\n") myrepl.start() From f6de5a941a4294da1b798d64818c4a202283aad7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 5 Jan 2020 13:59:40 +0100 Subject: [PATCH 0938/1650] Simplify version check Python 3 before 3.5 is no longer supported --- bpython/test/test_autocomplete.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index c1bf1edd9..c6ddb140a 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -21,8 +21,7 @@ from bpython._py3compat import py3 from bpython.test import mock -is_py34 = sys.version_info[:2] >= (3, 4) -if is_py34: +if is_py3: glob_function = "glob.iglob" else: glob_function = "glob.glob" @@ -430,7 +429,7 @@ def test_completions_starting_with_different_cases(self): ) self.assertSetEqual(matches, set(["ade"])) - @unittest.skipUnless(is_py34, "asyncio required") + @unittest.skipUnless(is_py3, "asyncio required") def test_issue_544(self): com = autocomplete.MultilineJediCompletion() code = "@asyncio.coroutine\ndef" From 716a92e58108b88038d1b9a3871d35030548a6b3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 5 Jan 2020 14:01:28 +0100 Subject: [PATCH 0939/1650] Fix version check --- bpython/test/test_autocomplete.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index c6ddb140a..0492428ec 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -21,7 +21,7 @@ from bpython._py3compat import py3 from bpython.test import mock -if is_py3: +if py3: glob_function = "glob.iglob" else: glob_function = "glob.glob" @@ -429,7 +429,7 @@ def test_completions_starting_with_different_cases(self): ) self.assertSetEqual(matches, set(["ade"])) - @unittest.skipUnless(is_py3, "asyncio required") + @unittest.skipUnless(py3, "asyncio required") def test_issue_544(self): com = autocomplete.MultilineJediCompletion() code = "@asyncio.coroutine\ndef" From 244228c524475bdba21f0fa20f021b0d2c75a578 Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Sun, 5 Jan 2020 13:10:05 +0000 Subject: [PATCH 0940/1650] Make translatable. --- bpython/cli.py | 8 +- bpython/curtsies.py | 4 +- bpython/translations/bpython.pot | 158 ++++++++------- .../translations/de/LC_MESSAGES/bpython.po | 182 ++++++++++-------- .../translations/es_ES/LC_MESSAGES/bpython.po | 152 ++++++++------- .../translations/fr_FR/LC_MESSAGES/bpython.po | 179 +++++++++-------- .../translations/it_IT/LC_MESSAGES/bpython.po | 152 ++++++++------- .../translations/nl_NL/LC_MESSAGES/bpython.po | 152 ++++++++------- bpython/urwid.py | 8 +- 9 files changed, 564 insertions(+), 431 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index ac4ee11a2..9aee3d5c6 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1991,14 +1991,18 @@ def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): # XXX these deprecation warnings need to go at some point clirepl.write( - "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. This backend has been deprecated in version 0.19 and might disappear in a future version." + _( + "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. This backend has been deprecated in version 0.19 and might disappear in a future version." + ) ) clirepl.write("\n") if sys.version_info[0] == 2: # XXX these deprecation warnings need to go at some point clirepl.write( - "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." + _( + "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." + ) ) clirepl.write("\n") diff --git a/bpython/curtsies.py b/bpython/curtsies.py index d108a2c96..b5921a109 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -205,7 +205,9 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): if sys.version_info[0] == 2: # XXX these deprecation warnings need to go at some point print( - "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." + _( + "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." + ) ) global repl diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 00f5329a0..99f58d7e2 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -1,302 +1,322 @@ # Translations template for bpython. -# Copyright (C) 2019 ORGANIZATION +# Copyright (C) 2020 ORGANIZATION # This file is distributed under the same license as the bpython project. -# FIRST AUTHOR , 2019. +# FIRST AUTHOR , 2020. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.19.dev6\n" +"Project-Id-Version: bpython 0.19.dev37\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2019-09-22 22:17+0200\n" +"POT-Creation-Date: 2020-01-05 13:08+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.6.0\n" +"Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:66 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:73 +#: bpython/args.py:81 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:75 +#: bpython/args.py:87 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:78 +#: bpython/args.py:95 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:80 +#: bpython/args.py:101 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "y" msgstr "" -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "yes" msgstr "" -#: bpython/cli.py:1705 +#: bpython/cli.py:1743 msgid "Rewind" msgstr "" -#: bpython/cli.py:1706 +#: bpython/cli.py:1744 msgid "Save" msgstr "" -#: bpython/cli.py:1707 +#: bpython/cli.py:1745 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1708 +#: bpython/cli.py:1746 msgid "Pager" msgstr "" -#: bpython/cli.py:1709 +#: bpython/cli.py:1747 msgid "Show Source" msgstr "" -#: bpython/curtsies.py:139 +#: bpython/cli.py:1994 +msgid "" +"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 +msgid "" +"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " +"been deprecated in version 0.19 and might disappear in a future version." +msgstr "" + +#: bpython/curtsies.py:152 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:141 +#: bpython/curtsies.py:158 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:222 +#: bpython/history.py:231 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:96 +#: bpython/paste.py:95 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:98 +#: bpython/paste.py:97 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:102 +#: bpython/paste.py:103 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:105 +#: bpython/paste.py:108 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:111 +#: bpython/paste.py:115 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:672 +#: bpython/repl.py:690 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:677 +#: bpython/repl.py:695 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:682 +#: bpython/repl.py:700 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:684 +#: bpython/repl.py:702 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:815 +#: bpython/repl.py:841 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 +#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:830 +#: bpython/repl.py:857 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:834 +#: bpython/repl.py:865 msgid "overwrite" msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:867 msgid "append" msgstr "" -#: bpython/repl.py:848 bpython/repl.py:1140 +#: bpython/repl.py:879 bpython/repl.py:1192 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:850 +#: bpython/repl.py:881 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:856 +#: bpython/repl.py:887 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:863 +#: bpython/repl.py:894 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:865 +#: bpython/repl.py:896 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:874 +#: bpython/repl.py:905 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:907 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:882 +#: bpython/repl.py:915 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:888 +#: bpython/repl.py:921 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:892 +#: bpython/repl.py:925 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:934 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:903 +#: bpython/repl.py:939 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:938 +#: bpython/repl.py:977 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:945 bpython/repl.py:949 +#: bpython/repl.py:985 bpython/repl.py:989 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:952 +#: bpython/repl.py:992 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1126 +#: bpython/repl.py:1172 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1148 +#: bpython/repl.py:1202 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1208 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:622 +#: bpython/urwid.py:628 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1177 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1182 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1190 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1143 +#: bpython/urwid.py:1195 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1146 +#: bpython/urwid.py:1204 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:339 +#: bpython/urwid.py:1396 +msgid "" +"WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:350 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:352 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:673 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:660 +#: bpython/curtsiesfrontend/repl.py:691 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:929 +#: bpython/curtsiesfrontend/repl.py:993 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:941 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:950 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:960 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:966 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:971 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:976 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index fdb8d8bc3..407a9df17 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,306 +7,328 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2019-09-22 22:17+0200\n" +"POT-Creation-Date: 2020-01-05 13:08+0000\n" "PO-Revision-Date: 2019-09-22 22:54+0200\n" "Last-Translator: Sebastian Ramacher \n" "Language: de\n" "Language-Team: de \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.6.0\n" -"X-Generator: Poedit 2.2.3\n" +"Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:66 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back to the " -"regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back " +"to the regular Python interpreter." msgstr "" "Verwendung: %prog [Optionen] [Datei [Argumente]]\n" -"Hinweis: Wenn bpython Argumente übergeben bekommt, die nicht verstanden werden, " -"wird der normale Python Interpreter ausgeführt." +"Hinweis: Wenn bpython Argumente übergeben bekommt, die nicht verstanden " +"werden, wird der normale Python Interpreter ausgeführt." -#: bpython/args.py:73 +#: bpython/args.py:81 msgid "Use CONFIG instead of default config file." msgstr "Verwende CONFIG antatt der standardmäßigen Konfigurationsdatei." -#: bpython/args.py:75 +#: bpython/args.py:87 msgid "Drop to bpython shell after running file instead of exiting." msgstr "Verbleibe in bpython nach dem Ausführen der Datei." -#: bpython/args.py:78 +#: bpython/args.py:95 msgid "Don't flush the output to stdout." msgstr "Gib Ausgabe beim Beenden nicht ernaut auf stdout aus." -#: bpython/args.py:80 +#: bpython/args.py:101 msgid "Print version and exit." msgstr "Zeige Versionsinformationen an und beende." -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "y" msgstr "j" -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "yes" msgstr "ja" -#: bpython/cli.py:1705 +#: bpython/cli.py:1743 msgid "Rewind" msgstr "Rückgängig" -#: bpython/cli.py:1706 +#: bpython/cli.py:1744 msgid "Save" msgstr "Speichern" -#: bpython/cli.py:1707 +#: bpython/cli.py:1745 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1708 +#: bpython/cli.py:1746 msgid "Pager" msgstr "" -#: bpython/cli.py:1709 +#: bpython/cli.py:1747 msgid "Show Source" msgstr "Quellcode anzeigen" -#: bpython/curtsies.py:139 +#: bpython/cli.py:1994 +msgid "" +"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 +msgid "" +"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " +"been deprecated in version 0.19 and might disappear in a future version." +msgstr "" + +#: bpython/curtsies.py:152 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:141 +#: bpython/curtsies.py:158 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:222 +#: bpython/history.py:231 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" -#: bpython/paste.py:96 +#: bpython/paste.py:95 msgid "Helper program not found." msgstr "Hilfsprogramm konnte nicht gefunden werden." -#: bpython/paste.py:98 +#: bpython/paste.py:97 msgid "Helper program could not be run." msgstr "Hilfsprogramm konnte nicht ausgeführt werden." -#: bpython/paste.py:102 +#: bpython/paste.py:103 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "Hilfsprogramm beendete mit Status %d." -#: bpython/paste.py:105 +#: bpython/paste.py:108 msgid "No output from helper program." msgstr "Keine Ausgabe von Hilfsprogramm vorhanden." -#: bpython/paste.py:111 +#: bpython/paste.py:115 msgid "Failed to recognize the helper program's output as an URL." msgstr "Konnte Ausgabe von Hilfsprogramm nicht verarbeiten." -#: bpython/repl.py:672 +#: bpython/repl.py:690 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:677 +#: bpython/repl.py:695 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" -#: bpython/repl.py:682 +#: bpython/repl.py:700 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/repl.py:684 +#: bpython/repl.py:702 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" -#: bpython/repl.py:815 +#: bpython/repl.py:841 msgid "Save to file (Esc to cancel): " msgstr "In Datei speichern (Esc um abzubrechen): " -#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 +#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 msgid "Save cancelled." msgstr "Speichern abgebrochen." -#: bpython/repl.py:830 +#: bpython/repl.py:857 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" -#: bpython/repl.py:834 +#: bpython/repl.py:865 msgid "overwrite" msgstr "überschreiben" -#: bpython/repl.py:836 +#: bpython/repl.py:867 msgid "append" msgstr "anhängen" -#: bpython/repl.py:848 bpython/repl.py:1140 +#: bpython/repl.py:879 bpython/repl.py:1192 #, python-format msgid "Error writing file '%s': %s" msgstr "Fehler beim Schreiben in Datei '%s': %s" -#: bpython/repl.py:850 +#: bpython/repl.py:881 #, python-format msgid "Saved to %s." msgstr "Nach %s gespeichert." -#: bpython/repl.py:856 +#: bpython/repl.py:887 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/repl.py:863 +#: bpython/repl.py:894 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/repl.py:865 +#: bpython/repl.py:896 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:874 +#: bpython/repl.py:905 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:907 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:882 +#: bpython/repl.py:915 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:888 +#: bpython/repl.py:921 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." -#: bpython/repl.py:892 +#: bpython/repl.py:925 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:900 +#: bpython/repl.py:934 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:903 +#: bpython/repl.py:939 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:938 +#: bpython/repl.py:977 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:945 bpython/repl.py:949 +#: bpython/repl.py:985 bpython/repl.py:989 msgid "Undo canceled" msgstr "Rückgängigmachen abgebrochen" -#: bpython/repl.py:952 +#: bpython/repl.py:992 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1126 +#: bpython/repl.py:1172 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt werden? (j/N)" +"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " +"werden? (j/N)" -#: bpython/repl.py:1148 +#: bpython/repl.py:1202 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -"bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die Änderungen " -"übernommen werden." +"bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " +"Änderungen übernommen werden." -#: bpython/repl.py:1151 +#: bpython/repl.py:1208 #, python-format msgid "Error editing config file: %s" msgstr "Fehler beim Bearbeiten der Konfigurationsdatei: %s" -#: bpython/urwid.py:622 +#: bpython/urwid.py:628 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1177 msgid "Run twisted reactor." msgstr "Führe twisted reactor aus." -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1182 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "Wähle reactor aus (siehe --help-reactors). Impliziert --twisted." -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1190 msgid "List available reactors for -r." msgstr "Liste verfügbare reactors für -r auf." -#: bpython/urwid.py:1143 +#: bpython/urwid.py:1195 msgid "" -"twistd plugin to run (use twistd for a list). Use \"--\" to pass further options " -"to the plugin." +"twistd plugin to run (use twistd for a list). Use \"--\" to pass further " +"options to the plugin." msgstr "" -"Auszuführendes twistd Plugin (starte twistd für eine Liste). Verwende \"--\" um " -"Optionen an das Plugin zu übergeben." +"Auszuführendes twistd Plugin (starte twistd für eine Liste). Verwende " +"\"--\" um Optionen an das Plugin zu übergeben." -#: bpython/urwid.py:1146 +#: bpython/urwid.py:1204 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:339 +#: bpython/urwid.py:1396 +msgid "" +"WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:350 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:352 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:673 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" -#: bpython/curtsiesfrontend/repl.py:660 +#: bpython/curtsiesfrontend/repl.py:691 #, python-format msgid "Reloaded at %s because %s modified." msgstr "Bei %s neugeladen, da %s modifiziert wurde." -#: bpython/curtsiesfrontend/repl.py:929 +#: bpython/curtsiesfrontend/repl.py:993 msgid "Session not reevaluated because it was not edited" msgstr "Die Sitzung wurde nicht neu ausgeführt, da sie nicht berabeitet wurde" -#: bpython/curtsiesfrontend/repl.py:941 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "Die Sitzung wurde nicht neu ausgeführt, da die gespeicherte Datei leer war" -#: bpython/curtsiesfrontend/repl.py:950 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "Sitzung bearbeitet und neu ausgeführt" -#: bpython/curtsiesfrontend/repl.py:960 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "Bei %s vom Benutzer neu geladen." -#: bpython/curtsiesfrontend/repl.py:966 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "Automatisches Neuladen deaktiviert." -#: bpython/curtsiesfrontend/repl.py:971 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "Automatisches Neuladen ist aktiv; beobachte Dateiänderungen..." -#: bpython/curtsiesfrontend/repl.py:976 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -"Automatisches Neuladen ist nicht verfügbar da watchdog nicht installiert ist." +"Automatisches Neuladen ist nicht verfügbar da watchdog nicht installiert " +"ist." + diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 5ede109be..331c672ac 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2019-09-22 22:17+0200\n" +"POT-Creation-Date: 2020-01-05 13:08+0000\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: es_ES\n" @@ -16,290 +16,310 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.6.0\n" +"Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:66 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:73 +#: bpython/args.py:81 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:75 +#: bpython/args.py:87 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:78 +#: bpython/args.py:95 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:80 +#: bpython/args.py:101 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "y" msgstr "s" -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "yes" msgstr "si" -#: bpython/cli.py:1705 +#: bpython/cli.py:1743 msgid "Rewind" msgstr "" -#: bpython/cli.py:1706 +#: bpython/cli.py:1744 msgid "Save" msgstr "" -#: bpython/cli.py:1707 +#: bpython/cli.py:1745 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1708 +#: bpython/cli.py:1746 msgid "Pager" msgstr "" -#: bpython/cli.py:1709 +#: bpython/cli.py:1747 msgid "Show Source" msgstr "" -#: bpython/curtsies.py:139 +#: bpython/cli.py:1994 +msgid "" +"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 +msgid "" +"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " +"been deprecated in version 0.19 and might disappear in a future version." +msgstr "" + +#: bpython/curtsies.py:152 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:141 +#: bpython/curtsies.py:158 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:222 +#: bpython/history.py:231 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:96 +#: bpython/paste.py:95 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:98 +#: bpython/paste.py:97 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:102 +#: bpython/paste.py:103 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:105 +#: bpython/paste.py:108 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:111 +#: bpython/paste.py:115 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:672 +#: bpython/repl.py:690 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:677 +#: bpython/repl.py:695 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:682 +#: bpython/repl.py:700 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:684 +#: bpython/repl.py:702 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:815 +#: bpython/repl.py:841 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 +#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:830 +#: bpython/repl.py:857 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:834 +#: bpython/repl.py:865 msgid "overwrite" msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:867 msgid "append" msgstr "" -#: bpython/repl.py:848 bpython/repl.py:1140 +#: bpython/repl.py:879 bpython/repl.py:1192 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:850 +#: bpython/repl.py:881 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:856 +#: bpython/repl.py:887 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:863 +#: bpython/repl.py:894 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:865 +#: bpython/repl.py:896 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:874 +#: bpython/repl.py:905 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:907 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:882 +#: bpython/repl.py:915 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:888 +#: bpython/repl.py:921 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:892 +#: bpython/repl.py:925 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:934 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:903 +#: bpython/repl.py:939 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:938 +#: bpython/repl.py:977 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:945 bpython/repl.py:949 +#: bpython/repl.py:985 bpython/repl.py:989 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:952 +#: bpython/repl.py:992 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1126 +#: bpython/repl.py:1172 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1148 +#: bpython/repl.py:1202 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1208 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:622 +#: bpython/urwid.py:628 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra el " "código fuente" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1177 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1182 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1190 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1143 +#: bpython/urwid.py:1195 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1146 +#: bpython/urwid.py:1204 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:339 +#: bpython/urwid.py:1396 +msgid "" +"WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:350 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:352 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:673 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:660 +#: bpython/curtsiesfrontend/repl.py:691 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:929 +#: bpython/curtsiesfrontend/repl.py:993 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:941 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:950 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:960 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:966 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:971 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:976 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index d16700c75..bc03d38d5 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,305 +6,326 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2019-09-22 22:17+0200\n" +"POT-Creation-Date: 2020-01-05 13:08+0000\n" "PO-Revision-Date: 2019-09-22 22:58+0200\n" "Last-Translator: Sebastian Ramacher \n" "Language: fr_FR\n" "Language-Team: bpython developers\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Plural-Forms: nplurals=2; plural=(n > 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.6.0\n" -"X-Generator: Poedit 2.2.3\n" +"Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:66 msgid "" "Usage: %prog [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back to the " -"regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back " +"to the regular Python interpreter." msgstr "" "Utilisation: %prog [options] [fichier [arguments]]\n" -"NOTE: Si bpython ne reconnaît pas un des arguments fournis, l'interpréteur " -"Python classique sera lancé" +"NOTE: Si bpython ne reconnaît pas un des arguments fournis, " +"l'interpréteur Python classique sera lancé" -#: bpython/args.py:73 +#: bpython/args.py:81 msgid "Use CONFIG instead of default config file." msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." -#: bpython/args.py:75 +#: bpython/args.py:87 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -"Aller dans le shell bpython après l'exécution du fichier au lieu de quitter." +"Aller dans le shell bpython après l'exécution du fichier au lieu de " +"quitter." -#: bpython/args.py:78 +#: bpython/args.py:95 msgid "Don't flush the output to stdout." msgstr "Ne pas purger la sortie vers stdout." -#: bpython/args.py:80 +#: bpython/args.py:101 msgid "Print version and exit." msgstr "Afficher la version et quitter." -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "y" msgstr "o" -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "yes" msgstr "oui" -#: bpython/cli.py:1705 +#: bpython/cli.py:1743 msgid "Rewind" msgstr "Rembobiner" -#: bpython/cli.py:1706 +#: bpython/cli.py:1744 msgid "Save" msgstr "Sauvegarder" -#: bpython/cli.py:1707 +#: bpython/cli.py:1745 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1708 +#: bpython/cli.py:1746 msgid "Pager" msgstr "" -#: bpython/cli.py:1709 +#: bpython/cli.py:1747 msgid "Show Source" msgstr "Montrer le code source" -#: bpython/curtsies.py:139 +#: bpython/cli.py:1994 +msgid "" +"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 +msgid "" +"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " +"been deprecated in version 0.19 and might disappear in a future version." +msgstr "" + +#: bpython/curtsies.py:152 msgid "log debug messages to bpython.log" msgstr "logger les messages de debug dans bpython.log" -#: bpython/curtsies.py:141 +#: bpython/curtsies.py:158 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:222 +#: bpython/history.py:231 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" -#: bpython/paste.py:96 +#: bpython/paste.py:95 msgid "Helper program not found." msgstr "programme externe non trouvé." -#: bpython/paste.py:98 +#: bpython/paste.py:97 msgid "Helper program could not be run." msgstr "impossible de lancer le programme externe." -#: bpython/paste.py:102 +#: bpython/paste.py:103 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "le programme externe a renvoyé un statut de sortie différent de zéro %d." -#: bpython/paste.py:105 +#: bpython/paste.py:108 msgid "No output from helper program." msgstr "pas de sortie du programme externe." -#: bpython/paste.py:111 +#: bpython/paste.py:115 msgid "Failed to recognize the helper program's output as an URL." msgstr "la sortie du programme externe ne correspond pas à une URL." -#: bpython/repl.py:672 +#: bpython/repl.py:690 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:677 +#: bpython/repl.py:695 #, python-format msgid "Cannot get source: %s" msgstr "Impossible de récupérer le source: %s" -#: bpython/repl.py:682 +#: bpython/repl.py:700 #, python-format msgid "Cannot access source of %r" msgstr "Impossible d'accéder au source de %r" -#: bpython/repl.py:684 +#: bpython/repl.py:702 #, python-format msgid "No source code found for %s" msgstr "Pas de code source trouvé pour %s" -#: bpython/repl.py:815 +#: bpython/repl.py:841 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 +#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:830 +#: bpython/repl.py:857 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:834 +#: bpython/repl.py:865 msgid "overwrite" msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:867 msgid "append" msgstr "" -#: bpython/repl.py:848 bpython/repl.py:1140 +#: bpython/repl.py:879 bpython/repl.py:1192 #, python-format msgid "Error writing file '%s': %s" msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" -#: bpython/repl.py:850 +#: bpython/repl.py:881 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:856 +#: bpython/repl.py:887 msgid "No clipboard available." msgstr "Pas de presse-papier disponible." -#: bpython/repl.py:863 +#: bpython/repl.py:894 msgid "Could not copy to clipboard." msgstr "Impossible de copier vers le presse-papier." -#: bpython/repl.py:865 +#: bpython/repl.py:896 msgid "Copied content to clipboard." msgstr "Contenu copié vers le presse-papier." -#: bpython/repl.py:874 +#: bpython/repl.py:905 msgid "Pastebin buffer? (y/N) " msgstr "Tampon Pastebin ? (o/N) " -#: bpython/repl.py:875 +#: bpython/repl.py:907 msgid "Pastebin aborted." msgstr "Pastebin abandonné." -#: bpython/repl.py:882 +#: bpython/repl.py:915 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" -#: bpython/repl.py:888 +#: bpython/repl.py:921 msgid "Posting data to pastebin..." msgstr "Envoi des donnés à pastebin..." -#: bpython/repl.py:892 +#: bpython/repl.py:925 #, python-format msgid "Upload failed: %s" msgstr "Echec du téléchargement: %s" -#: bpython/repl.py:900 +#: bpython/repl.py:934 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "URL Pastebin: %s - URL de suppression: %s" -#: bpython/repl.py:903 +#: bpython/repl.py:939 #, python-format msgid "Pastebin URL: %s" msgstr "URL Pastebin: %s" -#: bpython/repl.py:938 +#: bpython/repl.py:977 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:945 bpython/repl.py:949 +#: bpython/repl.py:985 bpython/repl.py:989 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:952 +#: bpython/repl.py:992 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1126 +#: bpython/repl.py:1172 msgid "Config file does not exist - create new from default? (y/N)" msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" -#: bpython/repl.py:1148 +#: bpython/repl.py:1202 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1208 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:622 +#: bpython/urwid.py:628 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -" <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> Montrer " -"Source " +" <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> " +"Montrer Source " -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1177 msgid "Run twisted reactor." msgstr "Lancer le reactor twisted." -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1182 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1190 msgid "List available reactors for -r." msgstr "Lister les reactors disponibles pour -r." -#: bpython/urwid.py:1143 +#: bpython/urwid.py:1195 msgid "" -"twistd plugin to run (use twistd for a list). Use \"--\" to pass further options " -"to the plugin." +"twistd plugin to run (use twistd for a list). Use \"--\" to pass further " +"options to the plugin." msgstr "" -"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" pour " -"donner plus d'options au plugin." +"plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" " +"pour donner plus d'options au plugin." -#: bpython/urwid.py:1146 +#: bpython/urwid.py:1204 msgid "Port to run an eval server on (forces Twisted)." msgstr "Port pour lancer un server eval (force Twisted)." -#: bpython/curtsiesfrontend/repl.py:339 +#: bpython/urwid.py:1396 +msgid "" +"WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:350 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:352 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:673 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" -#: bpython/curtsiesfrontend/repl.py:660 +#: bpython/curtsiesfrontend/repl.py:691 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:929 +#: bpython/curtsiesfrontend/repl.py:993 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:941 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:950 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:960 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:966 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:971 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:976 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" + diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index aaa50c495..f9a471d9d 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2019-09-22 22:17+0200\n" +"POT-Creation-Date: 2020-01-05 13:08+0000\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: it_IT\n" @@ -16,288 +16,308 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.6.0\n" +"Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:66 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:73 +#: bpython/args.py:81 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:75 +#: bpython/args.py:87 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:78 +#: bpython/args.py:95 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:80 +#: bpython/args.py:101 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "y" msgstr "s" -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "yes" msgstr "si" -#: bpython/cli.py:1705 +#: bpython/cli.py:1743 msgid "Rewind" msgstr "" -#: bpython/cli.py:1706 +#: bpython/cli.py:1744 msgid "Save" msgstr "" -#: bpython/cli.py:1707 +#: bpython/cli.py:1745 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1708 +#: bpython/cli.py:1746 msgid "Pager" msgstr "" -#: bpython/cli.py:1709 +#: bpython/cli.py:1747 msgid "Show Source" msgstr "" -#: bpython/curtsies.py:139 +#: bpython/cli.py:1994 +msgid "" +"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 +msgid "" +"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " +"been deprecated in version 0.19 and might disappear in a future version." +msgstr "" + +#: bpython/curtsies.py:152 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:141 +#: bpython/curtsies.py:158 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:222 +#: bpython/history.py:231 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:96 +#: bpython/paste.py:95 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:98 +#: bpython/paste.py:97 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:102 +#: bpython/paste.py:103 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:105 +#: bpython/paste.py:108 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:111 +#: bpython/paste.py:115 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:672 +#: bpython/repl.py:690 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:677 +#: bpython/repl.py:695 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:682 +#: bpython/repl.py:700 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:684 +#: bpython/repl.py:702 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:815 +#: bpython/repl.py:841 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 +#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:830 +#: bpython/repl.py:857 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:834 +#: bpython/repl.py:865 msgid "overwrite" msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:867 msgid "append" msgstr "" -#: bpython/repl.py:848 bpython/repl.py:1140 +#: bpython/repl.py:879 bpython/repl.py:1192 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:850 +#: bpython/repl.py:881 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:856 +#: bpython/repl.py:887 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:863 +#: bpython/repl.py:894 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:865 +#: bpython/repl.py:896 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:874 +#: bpython/repl.py:905 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:907 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:882 +#: bpython/repl.py:915 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:888 +#: bpython/repl.py:921 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:892 +#: bpython/repl.py:925 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:934 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:903 +#: bpython/repl.py:939 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:938 +#: bpython/repl.py:977 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:945 bpython/repl.py:949 +#: bpython/repl.py:985 bpython/repl.py:989 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:952 +#: bpython/repl.py:992 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1126 +#: bpython/repl.py:1172 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1148 +#: bpython/repl.py:1202 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1208 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:622 +#: bpython/urwid.py:628 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1177 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1182 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1190 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1143 +#: bpython/urwid.py:1195 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1146 +#: bpython/urwid.py:1204 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:339 +#: bpython/urwid.py:1396 +msgid "" +"WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:350 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:352 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:673 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:660 +#: bpython/curtsiesfrontend/repl.py:691 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:929 +#: bpython/curtsiesfrontend/repl.py:993 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:941 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:950 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:960 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:966 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:971 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:976 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index bd4e52d52..fabdfd591 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2019-09-22 22:17+0200\n" +"POT-Creation-Date: 2020-01-05 13:08+0000\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: nl_NL\n" @@ -16,288 +16,308 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.6.0\n" +"Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:66 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:73 +#: bpython/args.py:81 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:75 +#: bpython/args.py:87 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:78 +#: bpython/args.py:95 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:80 +#: bpython/args.py:101 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "y" msgstr "j" -#: bpython/cli.py:319 bpython/urwid.py:560 +#: bpython/cli.py:324 bpython/urwid.py:561 msgid "yes" msgstr "ja" -#: bpython/cli.py:1705 +#: bpython/cli.py:1743 msgid "Rewind" msgstr "" -#: bpython/cli.py:1706 +#: bpython/cli.py:1744 msgid "Save" msgstr "" -#: bpython/cli.py:1707 +#: bpython/cli.py:1745 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1708 +#: bpython/cli.py:1746 msgid "Pager" msgstr "" -#: bpython/cli.py:1709 +#: bpython/cli.py:1747 msgid "Show Source" msgstr "" -#: bpython/curtsies.py:139 +#: bpython/cli.py:1994 +msgid "" +"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 +msgid "" +"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " +"been deprecated in version 0.19 and might disappear in a future version." +msgstr "" + +#: bpython/curtsies.py:152 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:141 +#: bpython/curtsies.py:158 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:222 +#: bpython/history.py:231 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:96 +#: bpython/paste.py:95 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:98 +#: bpython/paste.py:97 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:102 +#: bpython/paste.py:103 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:105 +#: bpython/paste.py:108 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:111 +#: bpython/paste.py:115 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:672 +#: bpython/repl.py:690 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:677 +#: bpython/repl.py:695 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:682 +#: bpython/repl.py:700 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:684 +#: bpython/repl.py:702 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:815 +#: bpython/repl.py:841 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:817 bpython/repl.py:820 bpython/repl.py:839 +#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:830 +#: bpython/repl.py:857 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:834 +#: bpython/repl.py:865 msgid "overwrite" msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:867 msgid "append" msgstr "" -#: bpython/repl.py:848 bpython/repl.py:1140 +#: bpython/repl.py:879 bpython/repl.py:1192 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:850 +#: bpython/repl.py:881 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:856 +#: bpython/repl.py:887 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:863 +#: bpython/repl.py:894 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:865 +#: bpython/repl.py:896 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:874 +#: bpython/repl.py:905 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:907 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:882 +#: bpython/repl.py:915 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:888 +#: bpython/repl.py:921 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:892 +#: bpython/repl.py:925 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:934 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:903 +#: bpython/repl.py:939 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:938 +#: bpython/repl.py:977 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:945 bpython/repl.py:949 +#: bpython/repl.py:985 bpython/repl.py:989 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:952 +#: bpython/repl.py:992 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1126 +#: bpython/repl.py:1172 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1148 +#: bpython/repl.py:1202 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1208 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:622 +#: bpython/urwid.py:628 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1177 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1138 +#: bpython/urwid.py:1182 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1190 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1143 +#: bpython/urwid.py:1195 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1146 +#: bpython/urwid.py:1204 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/curtsiesfrontend/repl.py:339 +#: bpython/urwid.py:1396 +msgid "" +"WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." +msgstr "" + +#: bpython/curtsiesfrontend/repl.py:350 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:352 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:673 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:660 +#: bpython/curtsiesfrontend/repl.py:691 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:929 +#: bpython/curtsiesfrontend/repl.py:993 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:941 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:950 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:960 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:966 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:971 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:976 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/urwid.py b/bpython/urwid.py index 16cf6a605..eb91147af 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1393,14 +1393,18 @@ def start(main_loop, user_data): # XXX these deprecation warnings need to go at some point myrepl.write( - "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. This backend has been deprecated in version 0.19 and might disappear in a future version." + _( + "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. This backend has been deprecated in version 0.19 and might disappear in a future version." + ) ) myrepl.write("\n") if sys.version_info[0] == 2: # XXX these deprecation warnings need to go at some point myrepl.write( - "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." + _( + "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." + ) ) myrepl.write("\n") From 8d6ffe6265410f260ebc94b149523fbe702e3308 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 6 Jan 2020 12:17:34 +0100 Subject: [PATCH 0941/1650] Update German translation --- bpython/translations/de/LC_MESSAGES/bpython.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 407a9df17..56ba969c6 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" "POT-Creation-Date: 2020-01-05 13:08+0000\n" -"PO-Revision-Date: 2019-09-22 22:54+0200\n" +"PO-Revision-Date: 2020-01-06 12:17+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: de\n" "Language-Team: de \n" @@ -77,13 +77,13 @@ msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." -msgstr "" +msgstr "ACHTUNG: `bpython-cli` wird verwendet, die curses Implementierung von `bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv unterstützt und wird in einer zukünftigen Version entfernt werden." #: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 msgid "" "WARNING: You are using `bpython` on Python 2. Support for Python 2 has " "been deprecated in version 0.19 and might disappear in a future version." -msgstr "" +msgstr "ACHTUNG: `bpython` wird mit Python 2 verwendet. Diese Pythonversion wird seit Version 0.19 nicht mehr aktiv unterstützt und Unterstützung dafür wird in einer zukünftigen Version entfernt werden." #: bpython/curtsies.py:152 msgid "log debug messages to bpython.log" @@ -280,7 +280,7 @@ msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." -msgstr "" +msgstr "ACHTUNG: `bpython-urwid` wird verwendet, die curses Implementierung von `bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv unterstützt und wird in einer zukünftigen Version entfernt werden." #: bpython/curtsiesfrontend/repl.py:350 msgid "Welcome to bpython!" From 3ae9b1084d4abf688d2ff659d4fbf96131029148 Mon Sep 17 00:00:00 2001 From: Vilhelm Prytz Date: Mon, 6 Jan 2020 16:54:38 +0100 Subject: [PATCH 0942/1650] Remove unused imports --- bpython/curtsiesfrontend/coderunner.py | 1 - bpython/curtsiesfrontend/repl.py | 1 - 2 files changed, 2 deletions(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index ceaab8838..6087edb08 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -13,7 +13,6 @@ """ import code -import threading import signal import greenlet import logging diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d4b18e387..377692e43 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -11,7 +11,6 @@ import subprocess import sys import tempfile -import threading import time import unicodedata from six.moves import range From e9651aefe988626910e7c8da96e2ed04353980bd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Jan 2020 12:51:51 +0100 Subject: [PATCH 0943/1650] Immediately close file object --- bpython/importcompletion.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 6dea46125..35c2add66 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -43,6 +43,8 @@ SUFFIXES = importlib.machinery.all_suffixes() else: + import imp + SUFFIXES = [suffix for suffix, mode, type in imp.get_suffixes()] # The cached list of all known modules @@ -158,9 +160,15 @@ def find_modules(path): # Workaround for issue #166 continue try: + is_package = False with warnings.catch_warnings(): warnings.simplefilter("ignore", ImportWarning) fo, pathname, _ = imp.find_module(name, [path]) + if fo is not None: + fo.close() + else: + # Yay, package + is_package = True except (ImportError, IOError, SyntaxError): continue except UnicodeEncodeError: @@ -168,10 +176,7 @@ def find_modules(path): # invalid encoding continue else: - if fo is not None: - fo.close() - else: - # Yay, package + if is_package: for subname in find_modules(pathname): if subname != "__init__": yield "%s.%s" % (name, subname) From 1a674f00311523558d627eb333409afc6cfe0865 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Jan 2020 13:52:33 +0100 Subject: [PATCH 0944/1650] Use importlib for Python 3 (fixes #791) --- bpython/importcompletion.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 35c2add66..0fbc46d58 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -32,7 +32,6 @@ current_from_import_import, ) -import imp import os import sys import warnings @@ -143,6 +142,10 @@ def find_modules(path): filenames = os.listdir(path) except EnvironmentError: filenames = [] + + if py3: + finder = importlib.machinery.FileFinder(path) + for name in filenames: if not any(name.endswith(suffix) for suffix in SUFFIXES): # Possibly a package @@ -163,12 +166,22 @@ def find_modules(path): is_package = False with warnings.catch_warnings(): warnings.simplefilter("ignore", ImportWarning) - fo, pathname, _ = imp.find_module(name, [path]) - if fo is not None: - fo.close() + if py3: + spec = finder.find_spec(name) + if spec is None: + continue + if spec.submodule_search_locations is not None: + pathname = spec.submodule_search_locations[0] + is_package = True + else: + pathname = spec.origin else: - # Yay, package - is_package = True + fo, pathname, _ = imp.find_module(name, [path]) + if fo is not None: + fo.close() + else: + # Yay, package + is_package = True except (ImportError, IOError, SyntaxError): continue except UnicodeEncodeError: From 2d493de6448bbf1175398f0c79dacaffd7783d0b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Jan 2020 14:10:14 +0100 Subject: [PATCH 0945/1650] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d5e68083a..2425e6a96 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ Fixes: Thanks to Benedikt Rascher-Friesenhausen. * #776: Protect get_args from user code exceptions * Improve lock file handling on Windows +* #791: Use importlib instead of deprecated imp when running under Python 3 Support for Python 3.8 has been added. Support for Python 3.4 has been dropped. From 7908efdfdcca1e170345d2eeefe901cf76e64db2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Jan 2020 15:08:35 +0100 Subject: [PATCH 0946/1650] Blacken --- bpdb/__init__.py | 36 +++++---- bpdb/__main__.py | 3 +- bpdb/debugger.py | 11 +-- setup.py | 200 +++++++++++++++++++++++++---------------------- 4 files changed, 136 insertions(+), 114 deletions(-) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index 5806a5c37..b1696e51b 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -46,6 +46,7 @@ def set_trace(): # Adopted verbatim from pdb for completeness: + def post_mortem(t=None): # handling the default if t is None: @@ -53,8 +54,10 @@ def post_mortem(t=None): # being handled, otherwise it returns None t = sys.exc_info()[2] if t is None: - raise ValueError("A valid traceback must be passed if no " - "exception is being handled") + raise ValueError( + "A valid traceback must be passed if no " + "exception is being handled" + ) p = BPdb() p.reset() @@ -66,26 +69,28 @@ def pm(): def main(): - parser = OptionParser( - usage='Usage: %prog [options] [file [args]]') - parser.add_option('--version', '-V', action='store_true', - help='Print version and exit.') + parser = OptionParser(usage="Usage: %prog [options] [file [args]]") + parser.add_option( + "--version", "-V", action="store_true", help="Print version and exit." + ) options, args = parser.parse_args(sys.argv) if options.version: - print('bpdb on top of bpython version', __version__, end="") - print('on top of Python', sys.version.split()[0]) - print('(C) 2008-2013 Bob Farrell, Andreas Stuehrk et al. ' - 'See AUTHORS for detail.') + print("bpdb on top of bpython version", __version__, end="") + print("on top of Python", sys.version.split()[0]) + print( + "(C) 2008-2013 Bob Farrell, Andreas Stuehrk et al. " + "See AUTHORS for detail." + ) return 0 if len(args) < 2: - print('usage: bpdb scriptfile [arg] ...') + print("usage: bpdb scriptfile [arg] ...") return 2 # The following code is based on Python's pdb.py. mainpyfile = args[1] if not os.path.exists(mainpyfile): - print('Error:', mainpyfile, 'does not exist') + print("Error:", mainpyfile, "does not exist") return 1 # Hide bpdb from argument list. @@ -114,5 +119,8 @@ def main(): print("Running 'cont' or 'step' will restart the program") t = sys.exc_info()[2] pdb.interaction(None, t) - print("Post mortem debugger finished. The " + mainpyfile + - " will be restarted") + print( + "Post mortem debugger finished. The " + + mainpyfile + + " will be restarted" + ) diff --git a/bpdb/__main__.py b/bpdb/__main__.py index 758db27e7..0afaa17f8 100644 --- a/bpdb/__main__.py +++ b/bpdb/__main__.py @@ -26,6 +26,7 @@ import sys -if __name__ == '__main__': +if __name__ == "__main__": from . import main + sys.exit(main()) diff --git a/bpdb/debugger.py b/bpdb/debugger.py index ba802070b..0da7b1024 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -33,7 +33,7 @@ class BPdb(pdb.Pdb): def __init__(self, *args, **kwargs): pdb.Pdb.__init__(self, *args, **kwargs) - self.prompt = '(BPdb) ' + self.prompt = "(BPdb) " self.intro = 'Use "B" to enter bpython, Ctrl-d to exit it.' def postloop(self): @@ -46,14 +46,15 @@ def postloop(self): def do_Bpython(self, arg): locals_ = self.curframe.f_globals.copy() locals_.update(self.curframe.f_locals) - bpython.embed(locals_, ['-i']) - + bpython.embed(locals_, ["-i"]) def help_Bpython(self): print("B(python)") print("") - print("Invoke the bpython interpreter for this stack frame. To exit " - "bpython and return to a standard pdb press Ctrl-d") + print( + "Invoke the bpython interpreter for this stack frame. To exit " + "bpython and return to a standard pdb press Ctrl-d" + ) # shortcuts do_B = do_Bpython diff --git a/setup.py b/setup.py index f8f943339..66b20e376 100755 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ from babel.messages.frontend import extract_messages as _extract_messages from babel.messages.frontend import update_catalog as _update_catalog from babel.messages.frontend import init_catalog as _init_catalog + using_translations = True except ImportError: using_translations = False @@ -24,9 +25,10 @@ try: import sphinx from sphinx.setup_command import BuildDoc + # Sphinx 1.1.2 is buggy and building bpython with that version fails. # See #241. - using_sphinx = sphinx.__version__ >= '1.1.3' + using_sphinx = sphinx.__version__ >= "1.1.3" except ImportError: using_sphinx = False @@ -37,101 +39,104 @@ def git_describe_to_python_version(version): """Convert output from git describe to PEP 440 conforming versions.""" - version_info = version.split('-') + version_info = version.split("-") if len(version_info) < 2: - return 'unknown' + return "unknown" # we always have $version-$release release_type = version_info[1] version_data = { - 'version': version_info[0], - 'release_type': release_type, + "version": version_info[0], + "release_type": release_type, } if len(version_info) == 4: - version_data['commits'] = version_info[2] + version_data["commits"] = version_info[2] else: - version_data['commits'] = 0 + version_data["commits"] = 0 - if release_type == 'release': + if release_type == "release": if len(version_info) == 2: # format: $version-release # This is the case at time of the release. - fmt = '{version}' + fmt = "{version}" elif len(version_info) == 4: # format: $version-release-$commits-$hash # This is the case after a release. - fmt = '{version}-{commits}' - elif release_type == 'dev': + fmt = "{version}-{commits}" + elif release_type == "dev": # format: $version-dev-$commits-$hash or $version-dev - fmt = '{version}.dev{commits}' + fmt = "{version}.dev{commits}" else: - match = re.match(r'^(alpha|beta|rc)(\d*)$', release_type) + match = re.match(r"^(alpha|beta|rc)(\d*)$", release_type) if match is None: - return 'unknown' + return "unknown" if len(version_info) == 2: - fmt = '{version}{release_type}' + fmt = "{version}{release_type}" elif len(version_info) == 4: - fmt = '{version}{release_type}-{commits}' + fmt = "{version}{release_type}-{commits}" return fmt.format(**version_data) -version_file = 'bpython/_version.py' -version = 'unknown' +version_file = "bpython/_version.py" +version = "unknown" try: # get version from git describe - proc = subprocess.Popen(['git', 'describe', '--tags'], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + proc = subprocess.Popen( + ["git", "describe", "--tags"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) stdout = proc.communicate()[0].strip() if sys.version_info[0] > 2: - stdout = stdout.decode('ascii') + stdout = stdout.decode("ascii") if proc.returncode == 0: version = git_describe_to_python_version(stdout) except OSError: pass -if version == 'unknown': +if version == "unknown": try: # get version from existing version file with open(version_file) as vf: - version = vf.read().strip().split('=')[-1].replace('\'', '') + version = vf.read().strip().split("=")[-1].replace("'", "") version = version.strip() except IOError: pass -with open(version_file, 'w') as vf: - vf.write('# Auto-generated file, do not edit!\n') - vf.write('__version__ = \'%s\'\n' % (version, )) +with open(version_file, "w") as vf: + vf.write("# Auto-generated file, do not edit!\n") + vf.write("__version__ = '%s'\n" % (version,)) class install(_install): """Force install to run build target.""" def run(self): - self.run_command('build') + self.run_command("build") _install.run(self) -cmdclass = { - 'build': build, - 'install': install -} + +cmdclass = {"build": build, "install": install} from bpython import package_dir -translations_dir = os.path.join(package_dir, 'translations') + +translations_dir = os.path.join(package_dir, "translations") # localization options if using_translations: + class compile_catalog(_compile_catalog): def initialize_options(self): """Simply set default domain and directory attributes to the correct path for bpython.""" _compile_catalog.initialize_options(self) - self.domain = 'bpython' + self.domain = "bpython" self.directory = translations_dir self.use_fuzzy = True @@ -141,9 +146,9 @@ def initialize_options(self): correct path for bpython.""" _update_catalog.initialize_options(self) - self.domain = 'bpython' + self.domain = "bpython" self.output_dir = translations_dir - self.input_file = os.path.join(translations_dir, 'bpython.pot') + self.input_file = os.path.join(translations_dir, "bpython.pot") class extract_messages(_extract_messages): def initialize_options(self): @@ -151,8 +156,8 @@ def initialize_options(self): correct values for bpython.""" _extract_messages.initialize_options(self) - self.domain = 'bpython' - self.output_file = os.path.join(translations_dir, 'bpython.pot') + self.domain = "bpython" + self.output_file = os.path.join(translations_dir, "bpython.pot") class init_catalog(_init_catalog): def initialize_options(self): @@ -160,108 +165,115 @@ def initialize_options(self): attributes to the correct values for bpython.""" _init_catalog.initialize_options(self) - self.domain = 'bpython' + self.domain = "bpython" self.output_dir = translations_dir - self.input_file = os.path.join(translations_dir, 'bpython.pot') + self.input_file = os.path.join(translations_dir, "bpython.pot") - build.sub_commands.insert(0, ('compile_catalog', None)) + build.sub_commands.insert(0, ("compile_catalog", None)) - cmdclass['compile_catalog'] = compile_catalog - cmdclass['extract_messages'] = extract_messages - cmdclass['update_catalog'] = update_catalog - cmdclass['init_catalog'] = init_catalog + cmdclass["compile_catalog"] = compile_catalog + cmdclass["extract_messages"] = extract_messages + cmdclass["update_catalog"] = update_catalog + cmdclass["init_catalog"] = init_catalog if using_sphinx: + class BuildDocMan(BuildDoc): def initialize_options(self): BuildDoc.initialize_options(self) - self.builder = 'man' - self.source_dir = 'doc/sphinx/source' - self.build_dir = 'build' + self.builder = "man" + self.source_dir = "doc/sphinx/source" + self.build_dir = "build" - build.sub_commands.insert(0, ('build_sphinx_man', None)) - cmdclass['build_sphinx_man'] = BuildDocMan + build.sub_commands.insert(0, ("build_sphinx_man", None)) + cmdclass["build_sphinx_man"] = BuildDocMan - if platform.system() in ['FreeBSD', 'OpenBSD']: - man_dir = 'man' + if platform.system() in ["FreeBSD", "OpenBSD"]: + man_dir = "man" else: - man_dir = 'share/man' + man_dir = "share/man" # manual pages man_pages = [ - (os.path.join(man_dir, 'man1'), ['build/man/bpython.1']), - (os.path.join(man_dir, 'man5'), ['build/man/bpython-config.5']), + (os.path.join(man_dir, "man1"), ["build/man/bpython.1"]), + (os.path.join(man_dir, "man5"), ["build/man/bpython-config.5"]), ] else: man_pages = [] data_files = [ # desktop shortcut - (os.path.join('share', 'applications'), ['data/org.bpython-interpreter.bpython.desktop']), + ( + os.path.join("share", "applications"), + ["data/org.bpython-interpreter.bpython.desktop"], + ), # AppData - (os.path.join('share', 'appinfo'), ['data/org.bpython-interpreter.bpython.appdata.xml']), + ( + os.path.join("share", "appinfo"), + ["data/org.bpython-interpreter.bpython.appdata.xml"], + ), # icon - (os.path.join('share', 'pixmaps'), ['data/bpython.png']) + (os.path.join("share", "pixmaps"), ["data/bpython.png"]), ] data_files.extend(man_pages) classifiers = [ - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 3', + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", ] install_requires = [ - 'pygments', - 'requests', - 'curtsies >=0.1.18', - 'greenlet', - 'six >=1.5' + "pygments", + "requests", + "curtsies >=0.1.18", + "greenlet", + "six >=1.5", ] extras_require = { - 'urwid': ['urwid'], - 'watch': ['watchdog'], - 'jedi': ['jedi'], + "urwid": ["urwid"], + "watch": ["watchdog"], + "jedi": ["jedi"], # need requests[security] for SNI support (only before 2.7.7) ':python_full_version == "2.7.0" or ' - 'python_full_version == "2.7.1" or ' - 'python_full_version == "2.7.2" or ' - 'python_full_version == "2.7.3" or ' - 'python_full_version == "2.7.4" or ' - 'python_full_version == "2.7.5" or ' - 'python_full_version == "2.7.6"': [ - 'pyOpenSSL', - 'pyasn1', - 'ndg-httpsclient' - ] + 'python_full_version == "2.7.1" or ' + 'python_full_version == "2.7.2" or ' + 'python_full_version == "2.7.3" or ' + 'python_full_version == "2.7.4" or ' + 'python_full_version == "2.7.5" or ' + 'python_full_version == "2.7.6"': [ + "pyOpenSSL", + "pyasn1", + "ndg-httpsclient", + ], } packages = [ - 'bpython', - 'bpython.curtsiesfrontend', - 'bpython.test', - 'bpython.test.fodder', - 'bpython.translations', - 'bpdb' + "bpython", + "bpython.curtsiesfrontend", + "bpython.test", + "bpython.test.fodder", + "bpython.translations", + "bpdb", ] entry_points = { - 'console_scripts': [ - 'bpython = bpython.curtsies:main', - 'bpython-curses = bpython.cli:main', - 'bpython-urwid = bpython.urwid:main [urwid]', - 'bpdb = bpdb:main' + "console_scripts": [ + "bpython = bpython.curtsies:main", + "bpython-curses = bpython.cli:main", + "bpython-urwid = bpython.urwid:main [urwid]", + "bpdb = bpdb:main", ] } tests_require = [] if sys.version_info[0] == 2: - tests_require.append('mock') + tests_require.append("mock") # translations mo_files = [] for language in os.listdir(translations_dir): - mo_subpath = os.path.join(language, 'LC_MESSAGES', 'bpython.mo') + mo_subpath = os.path.join(language, "LC_MESSAGES", "bpython.mo") if os.path.exists(os.path.join(translations_dir, mo_subpath)): mo_files.append(mo_subpath) @@ -282,13 +294,13 @@ def initialize_options(self): packages=packages, data_files=data_files, package_data={ - 'bpython': ['sample-config'], - 'bpython.translations': mo_files, - 'bpython.test': ['test.config', 'test.theme'] + "bpython": ["sample-config"], + "bpython.translations": mo_files, + "bpython.test": ["test.config", "test.theme"], }, entry_points=entry_points, cmdclass=cmdclass, - test_suite='bpython.test' + test_suite="bpython.test", ) # vim: fileencoding=utf-8 sw=4 ts=4 sts=4 ai et sta From 0b7ab2c0c1d5eef258663a3ed2381d136b6f72b8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Jan 2020 15:14:56 +0100 Subject: [PATCH 0947/1650] Do not override builtin format --- bpython/curtsiesfrontend/repl.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 377692e43..066129dbf 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -15,7 +15,7 @@ import unicodedata from six.moves import range -from pygments import format +from pygments import format as pygformat from bpython._py3compat import PythonLexer from pygments.formatters import TerminalFormatter @@ -1126,7 +1126,7 @@ def push(self, line, insert_into_history=True): if self.config.syntax: display_line = bpythonparse( - format(self.tokenize(line), self.formatter) + pygformat(self.tokenize(line), self.formatter) ) # self.tokenize requires that the line not be in self.buffer yet @@ -1208,7 +1208,7 @@ def unhighlight_paren(self): self.highlighted_paren = None logger.debug("trying to unhighlight a paren on line %r", lineno) logger.debug("with these tokens: %r", saved_tokens) - new = bpythonparse(format(saved_tokens, self.formatter)) + new = bpythonparse(pygformat(saved_tokens, self.formatter)) self.display_buffer[lineno] = self.display_buffer[ lineno ].setslice_with_length( @@ -1276,7 +1276,7 @@ def current_line_formatted(self): """The colored current line (no prompt, not wrapped)""" if self.config.syntax: fs = bpythonparse( - format(self.tokenize(self.current_line), self.formatter) + pygformat(self.tokenize(self.current_line), self.formatter) ) if self.incr_search_mode: if self.incr_search_target in self.current_line: @@ -1766,7 +1766,7 @@ def reprint_line(self, lineno, tokens): logger.debug("calling reprint line with %r %r", lineno, tokens) if self.config.syntax: self.display_buffer[lineno] = bpythonparse( - format(tokens, self.formatter) + pygformat(tokens, self.formatter) ) def take_back_buffer_line(self): @@ -1920,7 +1920,7 @@ def show_source(self): self.status_bar.message("%s" % (e,)) else: if self.config.highlight_show_source: - source = format( + source = pygformat( PythonLexer().get_tokens(source), TerminalFormatter() ) self.pager(source) From e4a88c2c40c21c500d49c0f9f7f1cd029527f2f9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 29 Jan 2020 19:27:25 +0100 Subject: [PATCH 0948/1650] Use https --- ...org.bpython-interpreter.bpython.appdata.xml | 18 +++++++++--------- setup.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/data/org.bpython-interpreter.bpython.appdata.xml b/data/org.bpython-interpreter.bpython.appdata.xml index e24a09069..4830595be 100644 --- a/data/org.bpython-interpreter.bpython.appdata.xml +++ b/data/org.bpython-interpreter.bpython.appdata.xml @@ -22,32 +22,32 @@ org.bpython-interpreter.bpython.desktop - http://www.bpython-interpreter.org/ + https://www.bpython-interpreter.org/ https://github.com/bpython/bpython/issues - http://bpython-interpreter.org/images/8.png + https://bpython-interpreter.org/images/8.png - http://bpython-interpreter.org/images/1.png + https://bpython-interpreter.org/images/1.png - http://bpython-interpreter.org/images/2.png + https://bpython-interpreter.org/images/2.png - http://bpython-interpreter.org/images/3.png + https://bpython-interpreter.org/images/3.png - http://bpython-interpreter.org/images/4.png + https://bpython-interpreter.org/images/4.png - http://bpython-interpreter.org/images/5.png + https://bpython-interpreter.org/images/5.png - http://bpython-interpreter.org/images/6.png + https://bpython-interpreter.org/images/6.png - http://bpython-interpreter.org/images/7.png + https://bpython-interpreter.org/images/7.png bpython@googlegroups.com diff --git a/setup.py b/setup.py index 66b20e376..24ec50055 100755 --- a/setup.py +++ b/setup.py @@ -284,7 +284,7 @@ def initialize_options(self): author_email="robertanthonyfarrell@gmail.com", description="Fancy Interface to the Python Interpreter", license="MIT/X", - url="http://www.bpython-interpreter.org/", + url="https://www.bpython-interpreter.org/", long_description="""bpython is a fancy interface to the Python interpreter for Unix-like operating systems.""", classifiers=classifiers, From ad855aa4dd99cef3b81ea9d7357c668aa2bf46ff Mon Sep 17 00:00:00 2001 From: supakeen Date: Thu, 26 Mar 2020 20:31:52 +0000 Subject: [PATCH 0949/1650] Use the new pinnwand JSON API. --- bpython/config.py | 6 +---- bpython/paste.py | 26 +++++++++------------ bpython/repl.py | 2 -- doc/sphinx/source/configuration-options.rst | 23 ++++-------------- 4 files changed, 17 insertions(+), 40 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 456479b7a..f074fb745 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -81,9 +81,7 @@ def loadini(struct, configfile): "pastebin_confirm": True, "pastebin_expiry": "1week", "pastebin_helper": "", - "pastebin_removal_url": "https://bpaste.net/remove/$removal_id", - "pastebin_show_url": "https://bpaste.net/show/$paste_id", - "pastebin_url": "https://bpaste.net/json/new", + "pastebin_url": "https://bpaste.net", "save_append_py": False, "single_undo_time": 1.0, "syntax": True, @@ -224,8 +222,6 @@ def get_key_no_doublebind(command): struct.pastebin_confirm = config.getboolean("general", "pastebin_confirm") struct.pastebin_url = config.get("general", "pastebin_url") - struct.pastebin_show_url = config.get("general", "pastebin_show_url") - struct.pastebin_removal_url = config.get("general", "pastebin_removal_url") struct.pastebin_expiry = config.get("general", "pastebin_expiry") struct.pastebin_helper = config.get("general", "pastebin_helper") diff --git a/bpython/paste.py b/bpython/paste.py index b68e9688b..5bc30968c 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -40,35 +40,31 @@ class PasteFailed(Exception): class PastePinnwand(object): - def __init__(self, url, expiry, show_url, removal_url): + def __init__(self, url, expiry): self.url = url self.expiry = expiry - self.show_url = show_url - self.removal_url = removal_url def paste(self, s): """Upload to pastebin via json interface.""" - url = urljoin(self.url, "/json/new") - payload = {"code": s, "lexer": "pycon", "expiry": self.expiry} + url = urljoin(self.url, "/api/v1/paste") + payload = { + "expiry": self.expiry, + "files": [ + {"lexer": "pycon", "content": s} + ], + } try: - response = requests.post(url, data=payload, verify=True) + response = requests.post(url, json=payload, verify=True) response.raise_for_status() except requests.exceptions.RequestException as exc: raise PasteFailed(exc.message) data = response.json() - paste_url_template = Template(self.show_url) - paste_id = urlquote(data["paste_id"]) - paste_url = paste_url_template.safe_substitute(paste_id=paste_id) - - removal_url_template = Template(self.removal_url) - removal_id = urlquote(data["removal_id"]) - removal_url = removal_url_template.safe_substitute( - removal_id=removal_id - ) + paste_url = data["link"] + removal_url = data["removal"] return (paste_url, removal_url) diff --git a/bpython/repl.py b/bpython/repl.py index cfac71244..c3ce0bb08 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -477,8 +477,6 @@ def __init__(self, interp, config): self.paster = PastePinnwand( self.config.pastebin_url, self.config.pastebin_expiry, - self.config.pastebin_show_url, - self.config.pastebin_removal_url, ) @property diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index c51eb7efd..b1eeb068a 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -91,10 +91,9 @@ pastebin_helper ^^^^^^^^^^^^^^^ The name of a helper executable that should perform pastebin upload on bpython's -behalf. If set, this overrides `pastebin_url`. It also overrides -`pastebin_show_url`, as the helper is expected to return the full URL to the -pastebin as the first word of its output. The data is supplied to the helper via -STDIN. +behalf. If set, this overrides `pastebin_url`. The helper is expected to return +the full URL to the pastebin as the first word of its output. The data is +supplied to the helper via STDIN. An example helper program is ``pastebinit``, available for most systems. The following helper program can be used to create `gists @@ -141,23 +140,11 @@ following helper program can be used to create `gists .. versionadded:: 0.12 -pastebin_show_url -^^^^^^^^^^^^^^^^^ -The url under which the new paste can be reached. ``$paste_id`` will be replaced -by the ID of the new paste (default: https://bpaste.net/show/$paste_id/). - -pastebin_removal_url -^^^^^^^^^^^^^^^^^^^^ -The url under which a paste can be removed. ``$removal_id`` will be replaced by -the removal ID of the paste (default: https://bpaste.net/remova/$removal_id/). - -.. versionadded:: 0.14 - pastebin_url ^^^^^^^^^^^^ The pastebin url to post to (without a trailing slash). This pastebin has to be -a pastebin which uses provides a similar interface to ``bpaste.net``'s JSON -interface (default: https://bpaste.net/json/new). +a pastebin which provides a similar interface to ``bpaste.net``'s JSON +interface (default: https://bpaste.net). save_append_py ^^^^^^^^^^^^^^ From 74fe1a9b7189fcb663375b879a85f72d71a5e2b3 Mon Sep 17 00:00:00 2001 From: supakeen Date: Thu, 26 Mar 2020 20:32:43 +0000 Subject: [PATCH 0950/1650] Use the new `pinnwand` API. --- CHANGELOG | 1 + bpython/paste.py | 4 +--- bpython/repl.py | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2425e6a96..133dbc17e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ General information: * Usage in combination with Python 2 has been deprecated. This does not mean that support is dropped instantly but rather that at some point in the future we will stop running our testcases against Python 2. +* The new pinnwand API is used for the pastebin functionality. New features: diff --git a/bpython/paste.py b/bpython/paste.py index 5bc30968c..ec29a6da5 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -50,9 +50,7 @@ def paste(self, s): url = urljoin(self.url, "/api/v1/paste") payload = { "expiry": self.expiry, - "files": [ - {"lexer": "pycon", "content": s} - ], + "files": [{"lexer": "pycon", "content": s}], } try: diff --git a/bpython/repl.py b/bpython/repl.py index c3ce0bb08..f966b3faa 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -475,8 +475,7 @@ def __init__(self, interp, config): self.paster = PasteHelper(self.config.pastebin_helper) else: self.paster = PastePinnwand( - self.config.pastebin_url, - self.config.pastebin_expiry, + self.config.pastebin_url, self.config.pastebin_expiry, ) @property From 949c633e3350ebb653fcbbce6e0e9df3b7d9c584 Mon Sep 17 00:00:00 2001 From: supakeen Date: Sun, 29 Mar 2020 20:48:20 +0200 Subject: [PATCH 0951/1650] Mention removed configuration options. --- CHANGELOG | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 133dbc17e..3f410525d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,7 +10,10 @@ General information: * Usage in combination with Python 2 has been deprecated. This does not mean that support is dropped instantly but rather that at some point in the future we will stop running our testcases against Python 2. -* The new pinnwand API is used for the pastebin functionality. +* The new pinnwand API is used for the pastebin functionality. We have dropped + two configuration options: `pastebin_show_url` and `pastebin_removal_url`. If + you have your bpython configured to run against an old version of `pinnwand` + please update it. New features: From 584c3d099db8071e57b54dafa1dda2768a83901e Mon Sep 17 00:00:00 2001 From: supakeen Date: Sun, 29 Mar 2020 20:56:41 +0200 Subject: [PATCH 0952/1650] Slightly expand .gitignore with 'default' env. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index f5a04a965..1bda0f251 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ env .idea/ doc/sphinx/build/* bpython/_version.py +venv/ +.venv/ From 432c5c26cf7d5176f8fa8974a1d8f3a43d22d45a Mon Sep 17 00:00:00 2001 From: supakeen Date: Sun, 29 Mar 2020 21:03:27 +0200 Subject: [PATCH 0953/1650] Fix typo. --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2425e6a96..57c6a21a4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,7 +14,7 @@ General information: New features: Fixes: -* #765: Display correct signature for decorted functions. +* #765: Display correct signature for decorated functions. Thanks to Benedikt Rascher-Friesenhausen. * #776: Protect get_args from user code exceptions * Improve lock file handling on Windows From d96ad809739a91ff390a7a847f0e314fafe51844 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 18 Jan 2020 15:55:25 +0100 Subject: [PATCH 0954/1650] Drop 2.6 specific code --- bpython/test/test_args.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index f473a172f..e9c67e25b 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -35,7 +35,6 @@ def test_exec_dunder_file(self): f.flush() p = subprocess.Popen( [sys.executable] - + (["-W", "ignore"] if sys.version_info[:2] == (2, 6) else []) + ["-m", "bpython.curtsies", f.name], stderr=subprocess.PIPE, universal_newlines=True, From db237d4d766ae3f1af79d98e672a276b58850c94 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 30 Mar 2020 12:02:59 +0200 Subject: [PATCH 0955/1650] Start development of 0.20 --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 2486fb7ac..4b9bff5db 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,13 @@ Changelog ========= +0.20 +---- + +New features: + +Fixes: + 0.19 ---- From 6ff3d1ba6c835ee0b7188ebe46c8308cfb24df8f Mon Sep 17 00:00:00 2001 From: uriariel Date: Thu, 28 May 2020 02:34:42 +0300 Subject: [PATCH 0956/1650] Support packages that are using the new importlib.metadata api (fix of issue #807) --- bpython/curtsiesfrontend/repl.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 066129dbf..13c393783 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -274,6 +274,16 @@ def __init__(self, watcher, old_meta_path): self.watcher = watcher self.old_meta_path = old_meta_path + def find_distributions(self, context): + for finder in self.old_meta_path: + distribution_finder = getattr(finder, 'find_distributions', None) + if distribution_finder is not None: + loader = finder.find_distributions(context) + if loader is not None: + return loader + + return None + def find_module(self, fullname, path=None): for finder in self.old_meta_path: loader = finder.find_module(fullname, path) From 5bdf81228f7a8dafe4e4676b6e0f60c9825a8937 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 4 Jun 2020 21:28:54 +0200 Subject: [PATCH 0957/1650] travis: test with Python 3.9 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 879817cd4..8efd8e003 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ python: - "3.6" - "3.7" - "3.8" + - "3.9-dev" - "pypy" - "pypy3" @@ -20,6 +21,7 @@ env: matrix: allow_failures: + - python: "3.9-dev" - python: "pypy" - python: "pypy3" From 5739504da2860e8f9559ddbdc0cb57ed5d77eb7f Mon Sep 17 00:00:00 2001 From: supakeen Date: Wed, 10 Jun 2020 13:05:30 +0200 Subject: [PATCH 0958/1650] Use a newer GIF in the README. --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index e2f6583bf..b654fc187 100644 --- a/README.rst +++ b/README.rst @@ -20,10 +20,10 @@ to IDEs. These features include **syntax highlighting**, **expected parameter list**, **auto-indentation**, and **autocompletion**. (See below for example usage). -.. image:: http://i.imgur.com/jf8mCtP.gif +.. image:: https://bpython-interpreter.org/images/math.gif :alt: bpython - :width: 646 - :height: 300 + :width: 566 + :height: 348 :align: center bpython does **not** aim to be a complete IDE - the focus is on implementing a From d6bd2e5481bac81da4d9c23d435e7f2deb1b650c Mon Sep 17 00:00:00 2001 From: Etienne Richart <67104884+etiennerichart@users.noreply.github.com> Date: Thu, 18 Jun 2020 08:36:17 -0400 Subject: [PATCH 0959/1650] Fix project homepage link Link to http://bpython-interpreter.org. Same as homepage which appears earlier in the README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index b654fc187..0d19b0513 100644 --- a/README.rst +++ b/README.rst @@ -192,7 +192,7 @@ may be interested to try. .. _cffi docs: https://cffi.readthedocs.org/en/release-0.8/#macos-x .. _issues tracker: http://github.com/bpython/bpython/issues/ .. _pip: https://pip.pypa.io/en/latest/index.html -.. _project homepage: http://bpython-interpreter.org/community +.. _project homepage: http://bpython-interpreter.org .. _community: http://docs.bpython-interpreter.org/community.html .. _Robert: robertanthonyfarrell@gmail.com .. _bpython: http://www.bpython-interpreter.org/ From be1160a60c6a52eca9e3856838934bdf75a12769 Mon Sep 17 00:00:00 2001 From: Evan <49176014+allgo27@users.noreply.github.com> Date: Tue, 30 Jun 2020 09:01:02 -0500 Subject: [PATCH 0960/1650] Redo (#812) * redo * Updated tests with new redo-friendly variable names (insert_into_history -> new_code) * Added redo to sample-config --- bpython/config.py | 2 ++ bpython/curtsiesfrontend/repl.py | 25 +++++++++++++++++++------ bpython/repl.py | 7 ++++++- bpython/sample-config | 1 + bpython/test/test_curtsies_painting.py | 6 +++--- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index f074fb745..1a58bf525 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -108,6 +108,7 @@ def loadini(struct, configfile): "last_output": "F9", "left": "C-b", "pastebin": "F8", + "redo": "C-g", "reimport": "F6", "reverse_incremental_search": "M-r", "right": "C-f", @@ -193,6 +194,7 @@ def get_key_no_doublebind(command): struct.suspend_key = get_key_no_doublebind("suspend") struct.toggle_file_watch_key = get_key_no_doublebind("toggle_file_watch") struct.undo_key = get_key_no_doublebind("undo") + struct.redo_key = get_key_no_doublebind("redo") struct.reimport_key = get_key_no_doublebind("reimport") struct.reverse_incremental_search_key = get_key_no_doublebind( "reverse_incremental_search" diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 13c393783..4e8357b43 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -775,6 +775,8 @@ def process_key_event(self, e): self.on_tab(back=True) elif e in key_dispatch[self.config.undo_key]: # ctrl-r for undo self.prompt_undo() + elif e in key_dispatch[self.config.redo_key]: # ctrl-g for redo + self.redo() elif e in key_dispatch[self.config.save_key]: # ctrl-s for save greenlet.greenlet(self.write2file).switch() elif e in key_dispatch[self.config.pastebin_key]: # F8 for pastebin @@ -858,14 +860,17 @@ def readline_kill(self, e): else: self.cut_buffer = cut - def on_enter(self, insert_into_history=True, reset_rl_history=True): + def on_enter(self, new_code=True, reset_rl_history=True): # so the cursor isn't touching a paren TODO: necessary? + if new_code: + self.redo_stack = [] + self._set_cursor_offset(-1, update_completion=False) if reset_rl_history: self.rl_history.reset() self.history.append(self.current_line) - self.push(self.current_line, insert_into_history=insert_into_history) + self.push(self.current_line, insert_into_history=new_code) def on_tab(self, back=False): """Do something on tab key @@ -1019,7 +1024,7 @@ def send_session_to_external_editor(self, filename=None): source = preprocess("\n".join(from_editor), self.interp.compile) lines = source.split("\n") self.history = lines - self.reevaluate(insert_into_history=True) + self.reevaluate(new_code=True) self.current_line = current_line self.cursor_offset = len(self.current_line) self.status_bar.message(_("Session edited and reevaluated")) @@ -1030,7 +1035,7 @@ def clear_modules_and_reevaluate(self): cursor, line = self.cursor_offset, self.current_line for modname in set(sys.modules.keys()) - self.original_modules: del sys.modules[modname] - self.reevaluate(insert_into_history=True) + self.reevaluate(new_code=True) self.cursor_offset, self.current_line = cursor, line self.status_bar.message( _("Reloaded at %s by user.") % (time.strftime("%X"),) @@ -1811,7 +1816,15 @@ def prompt_for_undo(): greenlet.greenlet(prompt_for_undo).switch() - def reevaluate(self, insert_into_history=False): + def redo(self): + if (self.redo_stack): + temp = self.redo_stack.pop() + self.push(temp) + self.history.append(temp) + else: + self.status_bar.message("Nothing to redo.") + + def reevaluate(self, new_code=False): """bpython.Repl.undo calls this""" if self.watcher: self.watcher.reset() @@ -1835,7 +1848,7 @@ def reevaluate(self, insert_into_history=False): sys.stdin = ReevaluateFakeStdin(self.stdin, self) for line in old_logical_lines: self._current_line = line - self.on_enter(insert_into_history=insert_into_history) + self.on_enter(new_code=new_code) while self.fake_refresh_requested: self.fake_refresh_requested = False self.process_event(bpythonevents.RefreshRequestEvent()) diff --git a/bpython/repl.py b/bpython/repl.py index f966b3faa..96cf2b5d0 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -439,7 +439,8 @@ def __init__(self, interp, config): duplicates=config.hist_duplicates, hist_size=config.hist_length ) self.s_hist = [] - self.history = [] + self.history = [] # commands executed since beginning of session + self.redo_stack = [] self.evaluating = False self.matches_iter = MatchesIterator() self.funcprops = None @@ -1009,6 +1010,10 @@ def undo(self, n=1): entries = list(self.rl_history.entries) + #Most recently undone command + last_entries = self.history[-n:] + last_entries.reverse() + self.redo_stack += last_entries self.history = self.history[:-n] self.reevaluate() diff --git a/bpython/sample-config b/bpython/sample-config index 1f7c8dd65..00b6ee4ef 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -67,6 +67,7 @@ # toggle_file_watch = F5 # save = C-s # undo = C-r +# redo = C-g # up_one_line = C-p # down_one_line = C-n # cut_to_buffer = C-k diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 2c68b6092..15424c662 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -102,7 +102,7 @@ def test_run_line(self): orig_stdout = sys.stdout sys.stdout = self.repl.stdout [self.repl.add_normal_character(c) for c in "1 + 1"] - self.repl.on_enter(insert_into_history=False) + self.repl.on_enter(new_code=False) screen = fsarray([">>> 1 + 1", "2", "Welcome to"]) self.assert_paint_ignoring_formatting(screen, (1, 1)) finally: @@ -248,7 +248,7 @@ def enter(self, line=None): self.repl._set_cursor_offset(len(line), update_completion=False) self.repl.current_line = line with output_to_repl(self.repl): - self.repl.on_enter(insert_into_history=False) + self.repl.on_enter(new_code=False) self.assertEqual(self.repl.rl_history.entries, [""]) self.send_refreshes() @@ -592,7 +592,7 @@ def test_cursor_stays_at_bottom_of_screen(self): self.repl.width = 50 self.repl.current_line = "__import__('random').__name__" with output_to_repl(self.repl): - self.repl.on_enter(insert_into_history=False) + self.repl.on_enter(new_code=False) screen = [">>> __import__('random').__name__", "'random'"] self.assert_paint_ignoring_formatting(screen) From 471fa8223cd10b25cbc86ee2d192d29577241d8d Mon Sep 17 00:00:00 2001 From: Evan <49176014+allgo27@users.noreply.github.com> Date: Wed, 1 Jul 2020 10:11:37 -0500 Subject: [PATCH 0961/1650] updated new_code=True -> False in clear_modules_and_reevaluate (#816) --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 4e8357b43..9bccca5fe 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1035,7 +1035,7 @@ def clear_modules_and_reevaluate(self): cursor, line = self.cursor_offset, self.current_line for modname in set(sys.modules.keys()) - self.original_modules: del sys.modules[modname] - self.reevaluate(new_code=True) + self.reevaluate(new_code=False) self.cursor_offset, self.current_line = cursor, line self.status_bar.message( _("Reloaded at %s by user.") % (time.strftime("%X"),) From 8dd1ec42e58af2ebee891922387ef60deeb3c967 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 5 Jun 2020 10:50:17 +0200 Subject: [PATCH 0962/1650] simple_eval: handle ast.Constant This removes a deviation from Python's implementation. --- bpython/simpleeval.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 1f87b76c3..7e88ea28f 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -97,6 +97,8 @@ def simple_eval(node_or_string, namespace=None): node_or_string = node_or_string.body def _convert(node): + if py3 and isinstance(node, ast.Constant): + return node.value if isinstance(node, _string_type_nodes): return node.s elif isinstance(node, ast.Num): From ab6db6732565a992514c65c749e358439259d308 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 11 Jul 2020 17:38:41 +0200 Subject: [PATCH 0963/1650] travis: remove broken IRC notifications --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8efd8e003..5973aa401 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,6 @@ language: python sudo: false dist: xenial -notifications: - webhooks: - - secure: "QXcEHVnOi5mZpONkHSu1tydj8EK3G7xJ7Wv/WYhJ5soNUpEJgi6YwR1WcxSjo7qyi8hTL+4jc+ID0TpKDeS1lpXF41kG9xf5kdxw5OL0EnMkrP9okUN0Ip8taEhd8w+6+dGmfZrx2nXOg1kBU7W5cE90XYqEtNDVXXgNeilT+ik=" python: - "2.7" From 367c2f802a9f84e43f89d9eaefb4f273411877cc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 11 Jul 2020 17:44:32 +0200 Subject: [PATCH 0964/1650] Fix version check --- bpython/simpleeval.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 7e88ea28f..055aa92b8 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -32,6 +32,7 @@ import ast import inspect +import sys from six import string_types from six.moves import builtins @@ -97,7 +98,7 @@ def simple_eval(node_or_string, namespace=None): node_or_string = node_or_string.body def _convert(node): - if py3 and isinstance(node, ast.Constant): + if sys.version_info[:2] >= (3, 6) and isinstance(node, ast.Constant): return node.value if isinstance(node, _string_type_nodes): return node.s From 8e25e01ef42e4c12db7ce47cd6f0a950f442a00f Mon Sep 17 00:00:00 2001 From: Jack Rybarczyk <48132522+rybarczykj@users.noreply.github.com> Date: Mon, 13 Jul 2020 19:38:36 -0500 Subject: [PATCH 0965/1650] Corrected cursor positioning on inputs with full-width (2 column) characters (#817) - Added functionality for full-width character input - Works with normal input and stdin input - There still are issues with line wrapping when using full-width - Fix wide chars on input('...') --- bpython/curtsiesfrontend/repl.py | 19 +++++++++++++------ setup.py | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9bccca5fe..1e0c89ac0 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -19,6 +19,8 @@ from bpython._py3compat import PythonLexer from pygments.formatters import TerminalFormatter +from wcwidth import wcswidth + import blessings import curtsies @@ -1359,7 +1361,11 @@ def display_line_with_prompt(self): @property def current_cursor_line_without_suggestion(self): - """Current line, either output/input or Python prompt + code""" + """ + Current line, either output/input or Python prompt + code + + :returns: FmtStr + """ value = self.current_output_line + ( "" if self.coderunner.running else self.display_line_with_prompt ) @@ -1556,7 +1562,8 @@ def move_screen_up(current_line_start_row): if self.stdin.has_focus: cursor_row, cursor_column = divmod( - len(self.current_stdouterr_line) + self.stdin.cursor_offset, + wcswidth(self.current_stdouterr_line) + + wcswidth(self.stdin.current_line[: self.stdin.cursor_offset]), width, ) assert cursor_column >= 0, cursor_column @@ -1574,12 +1581,12 @@ def move_screen_up(current_line_start_row): len(self.current_line), self.cursor_offset, ) - else: + else: # Common case for determining cursor position cursor_row, cursor_column = divmod( ( - len(self.current_cursor_line_without_suggestion) - - len(self.current_line) - + self.cursor_offset + wcswidth(self.current_cursor_line_without_suggestion.s) + - wcswidth(self.current_line) + + wcswidth(self.current_line[: self.cursor_offset]) ), width, ) diff --git a/setup.py b/setup.py index 24ec50055..92365e442 100755 --- a/setup.py +++ b/setup.py @@ -228,6 +228,7 @@ def initialize_options(self): "curtsies >=0.1.18", "greenlet", "six >=1.5", + "wcwidth" ] extras_require = { From 883968102d2b8252e006e5374af906fac215c845 Mon Sep 17 00:00:00 2001 From: rybarczykj Date: Tue, 7 Jul 2020 10:12:23 -0500 Subject: [PATCH 0966/1650] add/integrate all_logical_lines to fix editor wrapping - send_session_to_editor no longer wraps in the middle of a logical line - added better documentation to some relevant existing methods --- bpython/curtsiesfrontend/repl.py | 66 +++++++++++++++++++++++++----- bpython/test/test_curtsies_repl.py | 2 + 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 1e0c89ac0..8301ad4ff 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -20,6 +20,7 @@ from pygments.formatters import TerminalFormatter from wcwidth import wcswidth +from math import ceil import blessings @@ -395,14 +396,22 @@ def __init__( # current line of output - stdout and stdin go here self.current_stdouterr_line = "" - # lines separated whenever logical line - # length goes over what the terminal width - # was at the time of original output + + # this is every line that's been displayed (input and output) + # as with formatting applied. Logical lines that exceeded the terminal width + # at the time of output are split across multiple entries in this list. self.display_lines = [] # this is every line that's been executed; it gets smaller on rewind self.history = [] + # This is every logical line that's been displayed, both input and output. + # Like self.history, lines are unwrapped, uncolored, and without prompt. + # Entries are tuples, where + # the first element a string of the line + # the second element is one of 2 strings: "input" or "output". + self.all_logical_lines = [] + # formatted version of lines in the buffer kept around so we can # unhighlight parens using self.reprint_line as called by bpython.Repl self.display_buffer = [] @@ -411,7 +420,7 @@ def __init__( # because there wasn't room to display everything self.scroll_offset = 0 - # from the left, 0 means first char + # cursor position relative to start of current_line, 0 is first char self._cursor_offset = 0 self.orig_tcattrs = orig_tcattrs @@ -872,8 +881,10 @@ def on_enter(self, new_code=True, reset_rl_history=True): self.rl_history.reset() self.history.append(self.current_line) + self.all_logical_lines.append((self.current_line, "input")) self.push(self.current_line, insert_into_history=new_code) + def on_tab(self, back=False): """Do something on tab key taken from bpython.cli @@ -982,6 +993,9 @@ def process_simple_keypress(self, e): self.add_normal_character(e) def send_current_block_to_external_editor(self, filename=None): + """" + Sends the current code block to external editor to be edited. Usually bound to C-x. + """ text = self.send_to_external_editor(self.get_current_block()) lines = [line for line in text.split("\n")] while lines and not lines[-1].split(): @@ -994,15 +1008,20 @@ def send_current_block_to_external_editor(self, filename=None): self.cursor_offset = len(self.current_line) def send_session_to_external_editor(self, filename=None): + """ + Sends entire bpython session to external editor to be edited. Usually bound to F7. + + Loops through self.all_logical_lines so that lines aren't wrapped in the editor. + + """ for_editor = EDIT_SESSION_HEADER for_editor += "\n".join( - line[len(self.ps1) :] - if line.startswith(self.ps1) - else line[len(self.ps2) :] - if line.startswith(self.ps2) - else "### " + line - for line in self.getstdout().split("\n") + line[0] if line[1] == "input" + else "### " + line[0] + for line in self.all_logical_lines ) + for_editor += "\n" + "### " + self.current_line + text = self.send_to_external_editor(for_editor) if text == for_editor: self.status_bar.message( @@ -1170,7 +1189,9 @@ def push(self, line, insert_into_history=True): if c: logger.debug("finished - buffer cleared") self.cursor_offset = 0 + self.display_lines.extend(self.display_buffer_lines) + self.display_buffer = [] self.buffer = [] @@ -1237,6 +1258,7 @@ def clear_current_block(self, remove_from_history=True): if remove_from_history: for unused in self.buffer: self.history.pop() + self.all_logical_lines.pop() self.buffer = [] self.cursor_offset = 0 self.saved_indent = 0 @@ -1244,6 +1266,9 @@ def clear_current_block(self, remove_from_history=True): self.cursor_offset = len(self.current_line) def get_current_block(self): + """ + Returns the current code block as string (without prompts) + """ return "\n".join(self.buffer + [self.current_line]) def send_to_stdouterr(self, output): @@ -1271,6 +1296,15 @@ def send_to_stdouterr(self, output): [], ) ) + + # These can be FmtStrs, but self.all_logical_lines only wants strings + for line in [self.current_stdouterr_line] + lines[1:-1]: + if isinstance(line, FmtStr): + self.all_logical_lines.append((line.s, "output")) + else: + self.all_logical_lines.append((line, "output")) + + self.current_stdouterr_line = lines[-1] logger.debug("display_lines: %r", self.display_lines) @@ -1804,11 +1838,15 @@ def take_back_buffer_line(self): self.display_buffer.pop() self.buffer.pop() self.history.pop() + self.all_logical_lines.pop() def take_back_empty_line(self): assert self.history and not self.history[-1] self.history.pop() self.display_lines.pop() + self.all_logical_lines.pop() + + def prompt_undo(self): if self.buffer: @@ -1826,8 +1864,9 @@ def prompt_for_undo(): def redo(self): if (self.redo_stack): temp = self.redo_stack.pop() - self.push(temp) self.history.append(temp) + self.all_logical_lines.append((temp, "input")) + self.push(temp) else: self.status_bar.message("Nothing to redo.") @@ -1839,6 +1878,7 @@ def reevaluate(self, new_code=False): old_display_lines = self.display_lines self.history = [] self.display_lines = [] + self.all_logical_lines = [] if not self.weak_rewind: self.interp = self.interp.__class__() @@ -1903,6 +1943,9 @@ def initialize_interp(self): del self.coderunner.interp.locals["_Helper"] def getstdout(self): + """ + Returns a string of the current bpython session, formatted and wrapped. + """ lines = self.lines_for_display + [self.current_line_formatted] s = ( "\n".join(x.s if isinstance(x, FmtStr) else x for x in lines) @@ -1911,6 +1954,7 @@ def getstdout(self): ) return s + def focus_on_subprocess(self, args): prev_sigwinch_handler = signal.getsignal(signal.SIGWINCH) try: diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 23f4b25b9..93cf2b5d3 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -78,6 +78,8 @@ def test_external_communication(self): def test_external_communication_encoding(self): with captured_output(): self.repl.display_lines.append('>>> "åß∂ƒ"') + self.repl.history.append('"åß∂ƒ"') + self.repl.all_logical_lines.append(('"åß∂ƒ"',"input")) self.repl.send_session_to_external_editor() def test_get_last_word(self): From e1733c5d4f832c0fd58874c425b4f789aa6a5bf2 Mon Sep 17 00:00:00 2001 From: rybarczykj Date: Mon, 20 Jul 2020 13:39:19 -0500 Subject: [PATCH 0967/1650] Use global Enum class to avoid typing strings - all_logical_lines uses tuples where the second val is "input" or "output". Using LineTypeTranslator avoids tying strings --- bpython/curtsiesfrontend/repl.py | 25 +++++++++---------------- bpython/repl.py | 14 +++++++++----- bpython/test/test_curtsies_repl.py | 3 ++- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 8301ad4ff..1d0265f84 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -31,6 +31,7 @@ import bpython from bpython.repl import Repl as BpythonRepl, SourceNotFound +from bpython.repl import LineTypeTranslator as LineType from bpython.config import ( Struct, loadini, @@ -396,7 +397,6 @@ def __init__( # current line of output - stdout and stdin go here self.current_stdouterr_line = "" - # this is every line that's been displayed (input and output) # as with formatting applied. Logical lines that exceeded the terminal width # at the time of output are split across multiple entries in this list. @@ -408,8 +408,9 @@ def __init__( # This is every logical line that's been displayed, both input and output. # Like self.history, lines are unwrapped, uncolored, and without prompt. # Entries are tuples, where - # the first element a string of the line - # the second element is one of 2 strings: "input" or "output". + # - the first element the line (string, not fmtsr) + # - the second element is one of 2 global constants: "input" or "output" + # (use LineType.INPUT or LineType.OUTPUT to avoid typing these strings) self.all_logical_lines = [] # formatted version of lines in the buffer kept around so we can @@ -881,10 +882,9 @@ def on_enter(self, new_code=True, reset_rl_history=True): self.rl_history.reset() self.history.append(self.current_line) - self.all_logical_lines.append((self.current_line, "input")) + self.all_logical_lines.append((self.current_line, LineType.INPUT)) self.push(self.current_line, insert_into_history=new_code) - def on_tab(self, back=False): """Do something on tab key taken from bpython.cli @@ -1016,7 +1016,7 @@ def send_session_to_external_editor(self, filename=None): """ for_editor = EDIT_SESSION_HEADER for_editor += "\n".join( - line[0] if line[1] == "input" + line[0] if line[1] == INPUT else "### " + line[0] for line in self.all_logical_lines ) @@ -1189,9 +1189,7 @@ def push(self, line, insert_into_history=True): if c: logger.debug("finished - buffer cleared") self.cursor_offset = 0 - self.display_lines.extend(self.display_buffer_lines) - self.display_buffer = [] self.buffer = [] @@ -1296,14 +1294,12 @@ def send_to_stdouterr(self, output): [], ) ) - # These can be FmtStrs, but self.all_logical_lines only wants strings for line in [self.current_stdouterr_line] + lines[1:-1]: if isinstance(line, FmtStr): - self.all_logical_lines.append((line.s, "output")) + self.all_logical_lines.append((line.s, LineType.OUTPUT)) else: - self.all_logical_lines.append((line, "output")) - + self.all_logical_lines.append((line, LineType.OUTPUT)) self.current_stdouterr_line = lines[-1] logger.debug("display_lines: %r", self.display_lines) @@ -1846,8 +1842,6 @@ def take_back_empty_line(self): self.display_lines.pop() self.all_logical_lines.pop() - - def prompt_undo(self): if self.buffer: return self.take_back_buffer_line() @@ -1865,7 +1859,7 @@ def redo(self): if (self.redo_stack): temp = self.redo_stack.pop() self.history.append(temp) - self.all_logical_lines.append((temp, "input")) + self.all_logical_lines.append((temp, LineType.INPUT)) self.push(temp) else: self.status_bar.message("Nothing to redo.") @@ -1954,7 +1948,6 @@ def getstdout(self): ) return s - def focus_on_subprocess(self, args): prev_sigwinch_handler = signal.getsignal(signal.SIGWINCH) try: diff --git a/bpython/repl.py b/bpython/repl.py index 96cf2b5d0..1c8672aa5 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -41,6 +41,7 @@ from itertools import takewhile from six import itervalues from types import ModuleType +from enum import Enum from pygments.token import Token @@ -387,6 +388,11 @@ def file_prompt(self, s): class SourceNotFound(Exception): """Exception raised when the requested source could not be found.""" +class LineTypeTranslator(Enum): + """ Used when adding a tuple to all_logical_lines, to get input / output values + having to actually type/know the strings """ + INPUT = "input" + OUTPUT = "output" class Repl(object): """Implements the necessary guff for a Python-repl-alike interface @@ -821,11 +827,9 @@ def formatforfile(self, session_ouput): output lines.""" def process(): - for line in session_ouput.split("\n"): - if line.startswith(self.ps1): - yield line[len(self.ps1) :] - elif line.startswith(self.ps2): - yield line[len(self.ps2) :] + for line, lineType in self.all_logical_lines: + if lineType == LineTypeTranslator.INPUT: + yield line elif line.rstrip(): yield "# OUT: %s" % (line,) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 93cf2b5d3..09db2d3b2 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -13,6 +13,7 @@ from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter from bpython.curtsiesfrontend import events as bpythonevents +from bpython.repl import LineTypeTranslator as LineType from bpython import autocomplete from bpython import config from bpython import args @@ -79,7 +80,7 @@ def test_external_communication_encoding(self): with captured_output(): self.repl.display_lines.append('>>> "åß∂ƒ"') self.repl.history.append('"åß∂ƒ"') - self.repl.all_logical_lines.append(('"åß∂ƒ"',"input")) + self.repl.all_logical_lines.append(('"åß∂ƒ"', LineType.INPUT)) self.repl.send_session_to_external_editor() def test_get_last_word(self): From 214e4ca63a9643a4a3f72081542fe187a0587ad8 Mon Sep 17 00:00:00 2001 From: rybarczykj Date: Tue, 21 Jul 2020 09:25:08 -0500 Subject: [PATCH 0968/1650] reconfigure and rename formatforfile - New name: get_session_formatted_for_file - No longer takes parameters as it can refer directly to all_logical_lines - send_session_to_external_editor now call this method instead of doing a very similar process - Now "# OUT:" refers to output and "### " refers to current line --- bpython/curtsiesfrontend/repl.py | 17 ++++------------- bpython/repl.py | 12 ++++++------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 1d0265f84..9c61c9faa 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -20,7 +20,6 @@ from pygments.formatters import TerminalFormatter from wcwidth import wcswidth -from math import ceil import blessings @@ -1009,18 +1008,10 @@ def send_current_block_to_external_editor(self, filename=None): def send_session_to_external_editor(self, filename=None): """ - Sends entire bpython session to external editor to be edited. Usually bound to F7. - - Loops through self.all_logical_lines so that lines aren't wrapped in the editor. - + Sends entire bpython session to external editor to be edited. Usually bound to F7. """ for_editor = EDIT_SESSION_HEADER - for_editor += "\n".join( - line[0] if line[1] == INPUT - else "### " + line[0] - for line in self.all_logical_lines - ) - for_editor += "\n" + "### " + self.current_line + for_editor += self.get_session_formatted_for_file() text = self.send_to_external_editor(for_editor) if text == for_editor: @@ -1035,7 +1026,7 @@ def send_session_to_external_editor(self, filename=None): current_line = lines[-1][4:] else: current_line = "" - from_editor = [line for line in lines if line[:3] != "###"] + from_editor = [line for line in lines if line[:6] != "# OUT:"] if all(not line.strip() for line in from_editor): self.status_bar.message( _("Session not reevaluated because saved file was blank") @@ -1938,7 +1929,7 @@ def initialize_interp(self): def getstdout(self): """ - Returns a string of the current bpython session, formatted and wrapped. + Returns a string of the current bpython session, wrapped, WITH prompts. """ lines = self.lines_for_display + [self.current_line_formatted] s = ( diff --git a/bpython/repl.py b/bpython/repl.py index 1c8672aa5..4ddb0a528 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -821,18 +821,18 @@ def line_is_empty(line): indentation = 0 return indentation - def formatforfile(self, session_ouput): + def get_session_formatted_for_file(self): """Format the stdout buffer to something suitable for writing to disk, i.e. without >>> and ... at input lines and with "# OUT: " prepended to - output lines.""" + output lines and "### " prepended to current line""" def process(): for line, lineType in self.all_logical_lines: if lineType == LineTypeTranslator.INPUT: yield line elif line.rstrip(): - yield "# OUT: %s" % (line,) - + yield "# OUT: %s" % line + yield "###: %s" % self.current_line return "\n".join(process()) def write2file(self): @@ -872,7 +872,7 @@ def write2file(self): self.interact.notify(_("Save cancelled.")) return - stdout_text = self.formatforfile(self.getstdout()) + stdout_text = self.get_session_formatted_for_file() try: with open(fn, mode) as f: @@ -889,7 +889,7 @@ def copy2clipboard(self): self.interact.notify(_("No clipboard available.")) return - content = self.formatforfile(self.getstdout()) + content = self.get_session_formatted_for_file() try: self.clipboard.copy(content) except CopyFailed: From 012ea90599f9883993d78a10a8eef7651b679755 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 22 Jul 2020 10:57:31 +0200 Subject: [PATCH 0969/1650] Apply black --- bpython/curtsiesfrontend/repl.py | 12 ++++++------ bpython/repl.py | 6 +++++- setup.py | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9c61c9faa..e060ff84c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -279,7 +279,7 @@ def __init__(self, watcher, old_meta_path): def find_distributions(self, context): for finder in self.old_meta_path: - distribution_finder = getattr(finder, 'find_distributions', None) + distribution_finder = getattr(finder, "find_distributions", None) if distribution_finder is not None: loader = finder.find_distributions(context) if loader is not None: @@ -404,7 +404,7 @@ def __init__( # this is every line that's been executed; it gets smaller on rewind self.history = [] - # This is every logical line that's been displayed, both input and output. + # This is every logical line that's been displayed, both input and output. # Like self.history, lines are unwrapped, uncolored, and without prompt. # Entries are tuples, where # - the first element the line (string, not fmtsr) @@ -1602,12 +1602,12 @@ def move_screen_up(current_line_start_row): len(self.current_line), self.cursor_offset, ) - else: # Common case for determining cursor position + else: # Common case for determining cursor position cursor_row, cursor_column = divmod( ( wcswidth(self.current_cursor_line_without_suggestion.s) - wcswidth(self.current_line) - + wcswidth(self.current_line[: self.cursor_offset]) + + wcswidth(self.current_line[: self.cursor_offset]) ), width, ) @@ -1832,7 +1832,7 @@ def take_back_empty_line(self): self.history.pop() self.display_lines.pop() self.all_logical_lines.pop() - + def prompt_undo(self): if self.buffer: return self.take_back_buffer_line() @@ -1847,7 +1847,7 @@ def prompt_for_undo(): greenlet.greenlet(prompt_for_undo).switch() def redo(self): - if (self.redo_stack): + if self.redo_stack: temp = self.redo_stack.pop() self.history.append(temp) self.all_logical_lines.append((temp, LineType.INPUT)) diff --git a/bpython/repl.py b/bpython/repl.py index 4ddb0a528..1c4b76f66 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -388,12 +388,15 @@ def file_prompt(self, s): class SourceNotFound(Exception): """Exception raised when the requested source could not be found.""" + class LineTypeTranslator(Enum): """ Used when adding a tuple to all_logical_lines, to get input / output values having to actually type/know the strings """ + INPUT = "input" OUTPUT = "output" + class Repl(object): """Implements the necessary guff for a Python-repl-alike interface @@ -833,6 +836,7 @@ def process(): elif line.rstrip(): yield "# OUT: %s" % line yield "###: %s" % self.current_line + return "\n".join(process()) def write2file(self): @@ -1014,7 +1018,7 @@ def undo(self, n=1): entries = list(self.rl_history.entries) - #Most recently undone command + # Most recently undone command last_entries = self.history[-n:] last_entries.reverse() self.redo_stack += last_entries diff --git a/setup.py b/setup.py index 92365e442..d2c10ce43 100755 --- a/setup.py +++ b/setup.py @@ -228,7 +228,7 @@ def initialize_options(self): "curtsies >=0.1.18", "greenlet", "six >=1.5", - "wcwidth" + "wcwidth", ] extras_require = { From b2111bda87aaf9dd799028add9e66fc36192cd4f Mon Sep 17 00:00:00 2001 From: Etienne Richart <67104884+etiennerichart@users.noreply.github.com> Date: Wed, 22 Jul 2020 12:24:30 -0400 Subject: [PATCH 0970/1650] Prevent symbolic link loops in import completion Use os.path.realpath while crawling the filesystem for import completion make sure that we are not in a loop. Resolves #806 --- bpython/importcompletion.py | 12 +++- bpython/test/test_import_not_cyclical.py | 92 ++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 bpython/test/test_import_not_cyclical.py diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 0fbc46d58..745a4cea6 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -48,6 +48,9 @@ # The cached list of all known modules modules = set() +# List of stored paths to compare against so that real paths are not repeated +# handles symlinks not mount points +paths = set() fully_loaded = False @@ -190,9 +193,12 @@ def find_modules(path): continue else: if is_package: - for subname in find_modules(pathname): - if subname != "__init__": - yield "%s.%s" % (name, subname) + path_real = os.path.realpath(pathname) + if path_real not in paths: + paths.add(path_real) + for subname in find_modules(pathname): + if subname != "__init__": + yield "%s.%s" % (name, subname) yield name diff --git a/bpython/test/test_import_not_cyclical.py b/bpython/test/test_import_not_cyclical.py new file mode 100644 index 000000000..4e2d99c35 --- /dev/null +++ b/bpython/test/test_import_not_cyclical.py @@ -0,0 +1,92 @@ +from bpython.test import unittest +from bpython.importcompletion import find_modules +import os, sys, tempfile + + +@unittest.skipIf(sys.version_info[0] <= 2, "Test doesn't work in python 2.") +class TestAvoidSymbolicLinks(unittest.TestCase): + def setUp(self): + with tempfile.TemporaryDirectory() as import_test_folder: + os.mkdir(os.path.join(import_test_folder, "Level0")) + os.mkdir(os.path.join(import_test_folder, "Right")) + os.mkdir(os.path.join(import_test_folder, "Left")) + + current_path = os.path.join(import_test_folder, "Level0") + with open( + os.path.join(current_path, "__init__.py"), "x" + ) as init_file: + pass + + current_path = os.path.join(current_path, "Level1") + os.mkdir(current_path) + with open( + os.path.join(current_path, "__init__.py"), "x" + ) as init_file: + pass + + current_path = os.path.join(current_path, "Level2") + os.mkdir(current_path) + with open( + os.path.join(current_path, "__init__.py"), "x" + ) as init_file: + pass + + os.symlink( + os.path.join(import_test_folder, "Level0/Level1"), + os.path.join(current_path, "Level3"), + True, + ) + + current_path = os.path.join(import_test_folder, "Right") + with open( + os.path.join(current_path, "__init__.py"), "x" + ) as init_file: + pass + + os.symlink( + os.path.join(import_test_folder, "Left"), + os.path.join(current_path, "toLeft"), + True, + ) + + current_path = os.path.join(import_test_folder, "Left") + with open( + os.path.join(current_path, "__init__.py"), "x" + ) as init_file: + pass + + os.symlink( + os.path.join(import_test_folder, "Right"), + os.path.join(current_path, "toRight"), + True, + ) + + self.foo = list(find_modules(os.path.abspath(import_test_folder))) + self.filepaths = [ + "Left.toRight.toLeft", + "Left.toRight", + "Left", + "Level0.Level1.Level2.Level3", + "Level0.Level1.Level2", + "Level0.Level1", + "Level0", + "Right", + "Right.toLeft", + "Right.toLeft.toRight", + ] + + def test_simple_symbolic_link_loop(self): + for thing in self.foo: + self.assertTrue(thing in self.filepaths) + if thing == "Left.toRight.toLeft": + self.filepaths.remove("Right.toLeft") + self.filepaths.remove("Right.toLeft.toRight") + if thing == "Right.toLeft.toRight": + self.filepaths.remove("Left.toRight.toLeft") + self.filepaths.remove("Left.toRight") + self.filepaths.remove(thing) + self.assertFalse(self.filepaths) + + +if __name__ == "__main__": + unittest.main() From e8cd5642d1def46d77abb8817bdde578ea0fb47a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 22 Jul 2020 23:22:06 +0200 Subject: [PATCH 0971/1650] Use bpython._py3compat Also rename self.foo to something more meaningful. --- bpython/test/test_import_not_cyclical.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_import_not_cyclical.py b/bpython/test/test_import_not_cyclical.py index 4e2d99c35..e1c0b045e 100644 --- a/bpython/test/test_import_not_cyclical.py +++ b/bpython/test/test_import_not_cyclical.py @@ -1,9 +1,15 @@ +# encoding: utf-8 + +import os +import sys +import tempfile + +from bpython._py3compat import py3 from bpython.test import unittest from bpython.importcompletion import find_modules -import os, sys, tempfile -@unittest.skipIf(sys.version_info[0] <= 2, "Test doesn't work in python 2.") +@unittest.skipIf(not py3, "Test doesn't work in python 2.") class TestAvoidSymbolicLinks(unittest.TestCase): def setUp(self): with tempfile.TemporaryDirectory() as import_test_folder: @@ -61,7 +67,7 @@ def setUp(self): True, ) - self.foo = list(find_modules(os.path.abspath(import_test_folder))) + self.modules = list(find_modules(os.path.abspath(import_test_folder))) self.filepaths = [ "Left.toRight.toLeft", "Left.toRight", @@ -76,7 +82,7 @@ def setUp(self): ] def test_simple_symbolic_link_loop(self): - for thing in self.foo: + for thing in self.modules: self.assertTrue(thing in self.filepaths) if thing == "Left.toRight.toLeft": self.filepaths.remove("Right.toLeft") From a590b47870a8bde93aa5eade1ae0fddb3e6c0c62 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 22 Jul 2020 23:26:57 +0200 Subject: [PATCH 0972/1650] Update CHANGELOG --- CHANGELOG | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 4b9bff5db..c08c5c2aa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,8 +5,16 @@ Changelog ---- New features: +* #802: Provide redo. + Thanks to Evan. Fixes: +* #806: Prevent symbolic link loops in import completion. + Thanks to Etienne Richart. +* #807: Support packages using importlib.metadata API. + Thanks to uriariel. +* #817: Fix cursor position with full-width characters. + Thanks to Jack Rybarczyk. 0.19 ---- From ac93c749804f953c4c93e3674e23089e5332dc83 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 24 Jul 2020 09:19:49 -0700 Subject: [PATCH 0973/1650] Remove Enum to maintain Python 2 support --- bpython/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 1c4b76f66..dddde6b48 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -41,7 +41,6 @@ from itertools import takewhile from six import itervalues from types import ModuleType -from enum import Enum from pygments.token import Token @@ -389,9 +388,10 @@ class SourceNotFound(Exception): """Exception raised when the requested source could not be found.""" -class LineTypeTranslator(Enum): +class LineTypeTranslator(object): """ Used when adding a tuple to all_logical_lines, to get input / output values having to actually type/know the strings """ + # TODO use Enum once we drop support for Python 2 INPUT = "input" OUTPUT = "output" From a453c02cf14f12360bc51efce5d687e89af61646 Mon Sep 17 00:00:00 2001 From: Tomas Babej Date: Fri, 31 Jul 2020 02:23:50 -0400 Subject: [PATCH 0974/1650] ImportFinder: Support find_spec interface --- bpython/curtsiesfrontend/repl.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index e060ff84c..841ce582a 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -287,6 +287,18 @@ def find_distributions(self, context): return None + def find_spec(self, fullname, path, target=None): + for finder in self.old_meta_path: + # Consider the finder only if it implements find_spec + if not getattr(finder, "find_spec", None): + continue + # Attempt to find the spec + spec = finder.find_spec(fullname, path, target) + if spec is not None: + # Patch the loader to enable reloading + spec.__loader__ = ImportLoader(self.watcher, spec.__loader__) + return spec + def find_module(self, fullname, path=None): for finder in self.old_meta_path: loader = finder.find_module(fullname, path) From 077a83784b0b45f40a0abfeefbd9bb54b7ae0db2 Mon Sep 17 00:00:00 2001 From: rybarczykj Date: Wed, 22 Jul 2020 10:26:20 -0500 Subject: [PATCH 0975/1650] Fix issue returning from editor --- bpython/curtsiesfrontend/repl.py | 2 +- bpython/repl.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 841ce582a..1eccf2ec6 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1038,7 +1038,7 @@ def send_session_to_external_editor(self, filename=None): current_line = lines[-1][4:] else: current_line = "" - from_editor = [line for line in lines if line[:6] != "# OUT:"] + from_editor = [line for line in lines if line[:6] != "# OUT:" and line[:3] != "###"] if all(not line.strip() for line in from_editor): self.status_bar.message( _("Session not reevaluated because saved file was blank") diff --git a/bpython/repl.py b/bpython/repl.py index dddde6b48..b1c1f238a 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -835,7 +835,7 @@ def process(): yield line elif line.rstrip(): yield "# OUT: %s" % line - yield "###: %s" % self.current_line + yield "### %s" % self.current_line return "\n".join(process()) From c39a9aba662ab15d632396b46a904531f010b3dc Mon Sep 17 00:00:00 2001 From: Etienne Richart Date: Thu, 30 Jul 2020 14:13:20 -0400 Subject: [PATCH 0976/1650] Add info on how to set default external editors --- bpython/sample-config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bpython/sample-config b/bpython/sample-config index 00b6ee4ef..af11e4172 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -36,6 +36,10 @@ # color_scheme = default # External editor to use for editing the current line, block, or full history +# Examples: vi (vim) +# code --wait (VS Code) - in VS Code use the command pallete to: +# Shell Command: Install 'code' command in PATH +# atom -nw (Atom) # Default is to try $EDITOR and $VISUAL, then vi - but if you uncomment # the line below that will take precedence # editor = vi From abc261a505b31d67b8867ada6092829a745f4142 Mon Sep 17 00:00:00 2001 From: Jack Rybarczyk <48132522+rybarczykj@users.noreply.github.com> Date: Tue, 4 Aug 2020 09:45:14 -0500 Subject: [PATCH 0977/1650] Refactoring to start using getattr_static (#832) Avoids having to use AttrCleaner in many contexts. Also adds some testing. --- bpython/autocomplete.py | 51 ++++++------- bpython/inspection.py | 47 +++++++++++- bpython/simpleeval.py | 46 +----------- bpython/test/test_autocomplete.py | 13 +++- bpython/test/test_inspection.py | 119 +++++++++++++++++++++++++++++ bpython/test/test_simpleeval.py | 120 ------------------------------ 6 files changed, 202 insertions(+), 194 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 53e1ea0f9..067a395f6 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -340,9 +340,6 @@ def attr_matches(self, text, namespace): """Taken from rlcompleter.py and bent to my will. """ - # Gna, Py 2.6's rlcompleter searches for __call__ inside the - # instance instead of the type, so we monkeypatch to prevent - # side-effects (__getattr__/__getattribute__) m = self.attr_matches_re.match(text) if not m: return [] @@ -356,19 +353,17 @@ def attr_matches(self, text, namespace): obj = safe_eval(expr, namespace) except EvaluationError: return [] - with inspection.AttrCleaner(obj): - matches = self.attr_lookup(obj, expr, attr) + matches = self.attr_lookup(obj, expr, attr) return matches def attr_lookup(self, obj, expr, attr): - """Second half of original attr_matches method factored out so it can - be wrapped in a safe try/finally block in case anything bad happens to - restore the original __getattribute__ method.""" + """Second half of attr_matches.""" words = self.list_attributes(obj) - if hasattr(obj, "__class__"): + if inspection.hasattr_safe(obj, "__class__"): words.append("__class__") - words = words + rlcompleter.get_class_members(obj.__class__) - if not isinstance(obj.__class__, abc.ABCMeta): + klass = inspection.getattr_safe(obj, "__class__") + words = words + rlcompleter.get_class_members(klass) + if not isinstance(klass, abc.ABCMeta): try: words.remove("__abstractmethods__") except ValueError: @@ -388,21 +383,25 @@ def attr_lookup(self, obj, expr, attr): if py3: def list_attributes(self, obj): - return dir(obj) + # TODO: re-implement dir using getattr_static to avoid using + # AttrCleaner here? + with inspection.AttrCleaner(obj): + return dir(obj) else: def list_attributes(self, obj): - if isinstance(obj, InstanceType): - try: + with inspection.AttrCleaner(obj): + if isinstance(obj, InstanceType): + try: + return dir(obj) + except Exception: + # This is a case where we can not prevent user code from + # running. We return a default list attributes on error + # instead. (#536) + return ["__doc__", "__module__"] + else: return dir(obj) - except Exception: - # This is a case where we can not prevent user code from - # running. We return a default list attributes on error - # instead. (#536) - return ["__doc__", "__module__"] - else: - return dir(obj) class DictKeyCompletion(BaseCompletionType): @@ -537,10 +536,9 @@ def matches(self, cursor_offset, line, **kwargs): obj = evaluate_current_expression(cursor_offset, line, locals_) except EvaluationError: return set() - with inspection.AttrCleaner(obj): - # strips leading dot - matches = [m[1:] for m in self.attr_lookup(obj, "", attr.word)] + # strips leading dot + matches = [m[1:] for m in self.attr_lookup(obj, "", attr.word)] return set(m for m in matches if few_enough_underscores(attr.word, m)) @@ -679,7 +677,6 @@ def get_completer_bpython(cursor_offset, line, **kwargs): def _callable_postfix(value, word): """rlcompleter's _callable_postfix done right.""" - with inspection.AttrCleaner(value): - if inspection.is_callable(value): - word += "(" + if inspection.is_callable(value): + word += "(" return word diff --git a/bpython/inspection.py b/bpython/inspection.py index 295a8a61f..72237a245 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -33,6 +33,7 @@ from six.moves import range from pygments.token import Token +from types import MemberDescriptorType from ._py3compat import PythonLexer, py3 from .lazyre import LazyReCompile @@ -214,7 +215,7 @@ def getpydocspec(f, func): if s is None: return None - if not hasattr(f, "__name__") or s.groups()[0] != f.__name__: + if not hasattr_safe(f, "__name__") or s.groups()[0] != f.__name__: return None args = list() @@ -276,8 +277,7 @@ def getfuncprops(func, f): argspec = ArgSpec(*argspec) fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError, ValueError): - with AttrCleaner(f): - argspec = getpydocspec(f, func) + argspec = getpydocspec(f, func) if argspec is None: return None if inspect.ismethoddescriptor(f): @@ -392,6 +392,47 @@ def get_encoding_file(fname): return "ascii" +if not py3: + + def getattr_safe(obj, name): + """side effect free getattr""" + if not is_new_style(obj): + return getattr(obj, name) + + with AttrCleaner(obj): + to_look_through = ( + obj.__mro__ + if inspect.isclass(obj) + else (obj,) + type(obj).__mro__ + ) + for cls in to_look_through: + if hasattr(cls, "__dict__") and name in cls.__dict__: + result = cls.__dict__[name] + if isinstance(result, MemberDescriptorType): + result = getattr(obj, name) + return result + raise AttributeError(name) + + +else: + + def getattr_safe(obj, name): + """side effect free getattr (calls getattr_static).""" + result = inspect.getattr_static(obj, name) + # Slots are a MemberDescriptorType + if isinstance(result, MemberDescriptorType): + result = getattr(obj, name) + return result + + +def hasattr_safe(obj, name): + try: + getattr_safe(obj, name) + return True + except AttributeError: + return False + + if py3: def get_source_unicode(obj): diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 055aa92b8..98ce41512 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -38,7 +38,7 @@ from . import line as line_properties from ._py3compat import py3 -from .inspection import is_new_style, AttrCleaner +from .inspection import getattr_safe _string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) _numeric_types = (int, float, complex) + (() if py3 else (long,)) @@ -163,7 +163,7 @@ def _convert(node): if isinstance(node, ast.Attribute): obj = _convert(node.value) attr = node.attr - return safe_get_attribute(obj, attr) + return getattr_safe(obj, attr) raise ValueError("malformed string") @@ -171,6 +171,7 @@ def _convert(node): def safe_getitem(obj, index): + """ Safely tries to access obj[index] """ if type(obj) in (list, tuple, dict, bytes) + string_types: try: return obj[index] @@ -251,44 +252,3 @@ def evaluate_current_attribute(cursor_offset, line, namespace=None): "can't lookup attribute %s on %r" % (attr.word, obj) ) - -def safe_get_attribute(obj, attr): - """Gets attributes without triggering descriptors on new-style classes""" - if is_new_style(obj): - with AttrCleaner(obj): - result = safe_get_attribute_new_style(obj, attr) - if isinstance(result, member_descriptor): - # will either be the same slot descriptor or the value - return getattr(obj, attr) - return result - return getattr(obj, attr) - - -class _ClassWithSlots(object): - __slots__ = ["a"] - - -member_descriptor = type(_ClassWithSlots.a) - - -def safe_get_attribute_new_style(obj, attr): - """Returns approximately the attribute returned by getattr(obj, attr) - - The object returned ought to be callable if getattr(obj, attr) was. - Fake callable objects may be returned instead, in order to avoid executing - arbitrary code in descriptors. - - If the object is an instance of a class that uses __slots__, will return - the member_descriptor object instead of the value. - """ - if not is_new_style(obj): - raise ValueError("%r is not a new-style class or object" % obj) - to_look_through = ( - obj.__mro__ if inspect.isclass(obj) else (obj,) + type(obj).__mro__ - ) - - for cls in to_look_through: - if hasattr(cls, "__dict__") and attr in cls.__dict__: - return cls.__dict__[attr] - - raise AttributeError() diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 0492428ec..3cc331ef1 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -251,6 +251,9 @@ def asserts_when_called(self): class Slots(object): __slots__ = ["a", "b"] +class OverriddenGetattribute(Foo): + def __getattribute__(self,name): + raise AssertionError("custom get attribute invoked") class TestAttrCompletion(unittest.TestCase): @classmethod @@ -298,12 +301,20 @@ def test_descriptor_attributes_not_run(self): com.matches(2, "a.", locals_={"a": Properties()}), set(["a.b", "a.a", "a.method", "a.asserts_when_called"]), ) + + def test_custom_get_attribute_not_invoked(self): + com = autocomplete.AttrCompletion() + self.assertSetEqual( + com.matches(2, "a.", locals_={"a": OverriddenGetattribute()}), + set(["a.b", "a.a", "a.method"]), + ) + def test_slots_not_crash(self): com = autocomplete.AttrCompletion() self.assertSetEqual( com.matches(2, "A.", locals_={"A": Slots}), - set(["A.b", "A.a", "A.mro"]), + set(["A.b", "A.a"]), ) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 096ae4cb0..08dda4b6c 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -155,5 +155,124 @@ def test_get_source_file(self): self.assertEqual(encoding, "utf-8") +class A(object): + a = "a" + + +class B(A): + b = "b" + + +class Property(object): + @property + def prop(self): + raise AssertionError("Property __get__ executed") + + +class Slots(object): + __slots__ = ["s1", "s2", "s3"] + + if not py3: + + @property + def s3(self): + raise AssertionError("Property __get__ executed") + + +class SlotsSubclass(Slots): + @property + def s4(self): + raise AssertionError("Property __get__ executed") + + +class OverriddenGetattr(object): + def __getattr__(self, attr): + raise AssertionError("custom __getattr__ executed") + + a = 1 + + +class OverriddenGetattribute(object): + def __getattribute__(self, attr): + raise AssertionError("custom __getattribute__ executed") + + a = 1 + + +class OverriddenMRO(object): + def __mro__(self): + raise AssertionError("custom mro executed") + + a = 1 + +member_descriptor = type(Slots.s1) + +class TestSafeGetAttribute(unittest.TestCase): + def test_lookup_on_object(self): + a = A() + a.x = 1 + self.assertEqual(inspection.getattr_safe(a, "x"), 1) + self.assertEqual(inspection.getattr_safe(a, "a"), "a") + b = B() + b.y = 2 + self.assertEqual(inspection.getattr_safe(b, "y"), 2) + self.assertEqual(inspection.getattr_safe(b, "a"), "a") + self.assertEqual(inspection.getattr_safe(b, "b"), "b") + + self.assertEqual(inspection.hasattr_safe(b, "y"), True) + self.assertEqual(inspection.hasattr_safe(b, "b"), True) + + + def test_avoid_running_properties(self): + p = Property() + self.assertEqual(inspection.getattr_safe(p, "prop"), Property.prop) + self.assertEqual(inspection.hasattr_safe(p, "prop"), True) + + def test_lookup_with_slots(self): + s = Slots() + s.s1 = "s1" + self.assertEqual(inspection.getattr_safe(s, "s1"), "s1") + with self.assertRaises(AttributeError): + inspection.getattr_safe(s, "s2") + + self.assertEqual(inspection.hasattr_safe(s, "s1"), True) + self.assertEqual(inspection.hasattr_safe(s, "s2"), False) + + def test_lookup_on_slots_classes(self): + sga = inspection.getattr_safe + s = SlotsSubclass() + self.assertIsInstance(sga(Slots, "s1"), member_descriptor) + self.assertIsInstance(sga(SlotsSubclass, "s1"), member_descriptor) + self.assertIsInstance(sga(SlotsSubclass, "s4"), property) + self.assertIsInstance(sga(s, "s4"), property) + + self.assertEqual(inspection.hasattr_safe(s, "s1"), False) + self.assertEqual(inspection.hasattr_safe(s, "s4"), True) + + @unittest.skipIf(py3, "Py 3 doesn't allow slots and prop in same class") + def test_lookup_with_property_and_slots(self): + sga = inspection.getattr_safe + s = SlotsSubclass() + self.assertIsInstance(sga(Slots, "s3"), property) + self.assertEqual(inspection.getattr_safe(s, "s3"), Slots.__dict__["s3"]) + self.assertIsInstance(sga(SlotsSubclass, "s3"), property) + + def test_lookup_on_overridden_methods(self): + sga = inspection.getattr_safe + self.assertEqual(sga(OverriddenGetattr(), "a"), 1) + self.assertEqual(sga(OverriddenGetattribute(), "a"), 1) + self.assertEqual(sga(OverriddenMRO(), "a"), 1) + with self.assertRaises(AttributeError): + sga(OverriddenGetattr(), "b") + with self.assertRaises(AttributeError): + sga(OverriddenGetattribute(), "b") + with self.assertRaises(AttributeError): + sga(OverriddenMRO(), "b") + + self.assertEqual(inspection.hasattr_safe(OverriddenGetattr(), "b"), False) + self.assertEqual(inspection.hasattr_safe(OverriddenGetattribute(), "b"), False) + self.assertEqual(inspection.hasattr_safe(OverriddenMRO(), "b"), False) + + if __name__ == "__main__": unittest.main() diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 9065f75cc..436517c4b 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -7,8 +7,6 @@ simple_eval, evaluate_current_expression, EvaluationError, - safe_get_attribute, - safe_get_attribute_new_style, ) from bpython.test import unittest from bpython._py3compat import py3 @@ -135,124 +133,6 @@ def test_with_namespace(self): self.assertCannotEval("a[1].a|bc", {}) -class A(object): - a = "a" - - -class B(A): - b = "b" - - -class Property(object): - @property - def prop(self): - raise AssertionError("Property __get__ executed") - - -class Slots(object): - __slots__ = ["s1", "s2", "s3"] - - if not py3: - - @property - def s3(self): - raise AssertionError("Property __get__ executed") - - -class SlotsSubclass(Slots): - @property - def s4(self): - raise AssertionError("Property __get__ executed") - - -class OverriddenGetattr(object): - def __getattr__(self, attr): - raise AssertionError("custom __getattr__ executed") - - a = 1 - - -class OverriddenGetattribute(object): - def __getattribute__(self, attr): - raise AssertionError("custom __getattribute__ executed") - - a = 1 - - -class OverriddenMRO(object): - def __mro__(self): - raise AssertionError("custom mro executed") - - a = 1 - - -member_descriptor = type(Slots.s1) - - -class TestSafeGetAttribute(unittest.TestCase): - def test_lookup_on_object(self): - a = A() - a.x = 1 - self.assertEqual(safe_get_attribute_new_style(a, "x"), 1) - self.assertEqual(safe_get_attribute_new_style(a, "a"), "a") - b = B() - b.y = 2 - self.assertEqual(safe_get_attribute_new_style(b, "y"), 2) - self.assertEqual(safe_get_attribute_new_style(b, "a"), "a") - self.assertEqual(safe_get_attribute_new_style(b, "b"), "b") - - def test_avoid_running_properties(self): - p = Property() - self.assertEqual(safe_get_attribute_new_style(p, "prop"), Property.prop) - - @unittest.skipIf(py3, "Old-style classes not in Python 3") - def test_raises_on_old_style_class(self): - class Old: - pass - - with self.assertRaises(ValueError): - safe_get_attribute_new_style(Old, "asdf") - - def test_lookup_with_slots(self): - s = Slots() - s.s1 = "s1" - self.assertEqual(safe_get_attribute(s, "s1"), "s1") - self.assertIsInstance( - safe_get_attribute_new_style(s, "s1"), member_descriptor - ) - with self.assertRaises(AttributeError): - safe_get_attribute(s, "s2") - self.assertIsInstance( - safe_get_attribute_new_style(s, "s2"), member_descriptor - ) - - def test_lookup_on_slots_classes(self): - sga = safe_get_attribute - s = SlotsSubclass() - self.assertIsInstance(sga(Slots, "s1"), member_descriptor) - self.assertIsInstance(sga(SlotsSubclass, "s1"), member_descriptor) - self.assertIsInstance(sga(SlotsSubclass, "s4"), property) - self.assertIsInstance(sga(s, "s4"), property) - - @unittest.skipIf(py3, "Py 3 doesn't allow slots and prop in same class") - def test_lookup_with_property_and_slots(self): - sga = safe_get_attribute - s = SlotsSubclass() - self.assertIsInstance(sga(Slots, "s3"), property) - self.assertEqual(safe_get_attribute(s, "s3"), Slots.__dict__["s3"]) - self.assertIsInstance(sga(SlotsSubclass, "s3"), property) - - def test_lookup_on_overridden_methods(self): - sga = safe_get_attribute - self.assertEqual(sga(OverriddenGetattr(), "a"), 1) - self.assertEqual(sga(OverriddenGetattribute(), "a"), 1) - self.assertEqual(sga(OverriddenMRO(), "a"), 1) - with self.assertRaises(AttributeError): - sga(OverriddenGetattr(), "b") - with self.assertRaises(AttributeError): - sga(OverriddenGetattribute(), "b") - with self.assertRaises(AttributeError): - sga(OverriddenMRO(), "b") if __name__ == "__main__": From 0735e9180e0ac456bc01eca59071b98ed6597143 Mon Sep 17 00:00:00 2001 From: rybarczykj Date: Wed, 5 Aug 2020 14:34:04 -0500 Subject: [PATCH 0978/1650] update curtsies version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d2c10ce43..33f650a1c 100755 --- a/setup.py +++ b/setup.py @@ -225,7 +225,7 @@ def initialize_options(self): install_requires = [ "pygments", "requests", - "curtsies >=0.1.18", + "curtsies >=0.3.0", "greenlet", "six >=1.5", "wcwidth", From 93cfe58b5c2a1a3095585c3b8a22a4f25e99b88d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 10 Aug 2020 14:42:49 +0200 Subject: [PATCH 0979/1650] Bump curtsies requirement in README and requirements.txt --- README.rst | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 0d19b0513..9f3372a4f 100644 --- a/README.rst +++ b/README.rst @@ -97,7 +97,7 @@ Dependencies ============ * Pygments * requests -* curtsies >= 0.1.18 +* curtsies >= 0.3.0 * greenlet * six >= 1.5 * Sphinx != 1.1.2 (optional, for the documentation) diff --git a/requirements.txt b/requirements.txt index 78619ff24..25eb37004 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Pygments -curtsies >=0.1.18 +curtsies >=0.3.0 greenlet requests setuptools From 84d581af5def8d4157643130ce59349b0c26fd70 Mon Sep 17 00:00:00 2001 From: rybarczykj Date: Thu, 13 Aug 2020 14:42:25 -0500 Subject: [PATCH 0980/1650] rename s_hist to screen_hist --- bpython/cli.py | 23 ++++++++++++----------- bpython/repl.py | 4 +++- bpython/urwid.py | 5 ----- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 9aee3d5c6..dc3ebc829 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -967,7 +967,8 @@ def p_key(self, key): return "" elif key in key_dispatch[config.clear_screen_key]: - self.s_hist = [self.s_hist[-1]] + # clear all but current line + self.screen_hist = [self.screen_hist[-1]] self.highlighted_paren = None self.redraw() return "" @@ -1099,7 +1100,7 @@ def prompt(self, more): self.stdout_hist += self.ps1 else: self.stdout_hist += self.ps1.encode(getpreferredencoding()) - self.s_hist.append( + self.screen_hist.append( "\x01%s\x03%s\x04" % (self.config.color_scheme["prompt"], self.ps1) ) @@ -1110,7 +1111,7 @@ def prompt(self, more): self.stdout_hist += self.ps2 else: self.stdout_hist += self.ps2.encode(getpreferredencoding()) - self.s_hist.append( + self.screen_hist.append( "\x01%s\x03%s\x04" % (prompt_more_color, self.ps2) ) @@ -1128,15 +1129,15 @@ def push(self, s, insert_into_history=True): curses.raw(True) def redraw(self): - """Redraw the screen.""" + """Redraw the screen using screen_hist""" self.scr.erase() - for k, s in enumerate(self.s_hist): + for k, s in enumerate(self.screen_hist): if not s: continue self.iy, self.ix = self.scr.getyx() for i in s.split("\x04"): self.echo(i, redraw=False) - if k < len(self.s_hist) - 1: + if k < len(self.screen_hist) - 1: self.scr.addstr("\n") self.iy, self.ix = self.scr.getyx() self.print_line(self.s) @@ -1175,7 +1176,7 @@ def repl(self): return self.exit_value self.history.append(inp) - self.s_hist[-1] += self.f_string + self.screen_hist[-1] += self.f_string if py3: self.stdout_hist += inp + "\n" else: @@ -1234,7 +1235,7 @@ def reevaluate(self): self.f_string = "" self.buffer = [] self.scr.erase() - self.s_hist = [] + self.screen_hist = [] # Set cursor position to -1 to prevent paren matching self.cpos = -1 @@ -1247,7 +1248,7 @@ def reevaluate(self): else: self.stdout_hist += line.encode(getpreferredencoding()) + "\n" self.print_line(line) - self.s_hist[-1] += self.f_string + self.screen_hist[-1] += self.f_string # I decided it was easier to just do this manually # than to make the print_line and history stuff more flexible. self.scr.addstr("\n") @@ -1288,7 +1289,7 @@ def write(self, s): self.stdout_hist += t self.echo(s) - self.s_hist.append(s.rstrip()) + self.screen_hist.append(s.rstrip()) def show_list( self, items, arg_pos, topline=None, formatter=None, current_item=None @@ -1551,7 +1552,7 @@ def send_current_line_to_editor(self): self.stdout_hist += line.encode(getpreferredencoding()) + "\n" self.history.append(line) self.print_line(line) - self.s_hist[-1] += self.f_string + self.screen_hist[-1] += self.f_string self.scr.addstr("\n") self.more = self.push(line) self.prompt(self.more) diff --git a/bpython/repl.py b/bpython/repl.py index b1c1f238a..353aafe1d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -447,7 +447,9 @@ def __init__(self, interp, config): self.rl_history = History( duplicates=config.hist_duplicates, hist_size=config.hist_length ) - self.s_hist = [] + # all input and output, stored as old style format strings + # (\x01, \x02, ...) for cli.py + self.screen_hist = [] self.history = [] # commands executed since beginning of session self.redo_stack = [] self.evaluating = False diff --git a/bpython/urwid.py b/bpython/urwid.py index eb91147af..b33dc1f22 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -908,7 +908,6 @@ def reevaluate(self): self.f_string = "" self.buffer = [] self.scr.erase() - self.s_hist = [] # Set cursor position to -1 to prevent paren matching self.cpos = -1 @@ -923,7 +922,6 @@ def reevaluate(self): line.encode(locale.getpreferredencoding()) + "\n" ) self.print_line(line) - self.s_hist[-1] += self.f_string # I decided it was easier to just do this manually # than to make the print_line and history stuff more flexible. self.scr.addstr("\n") @@ -964,7 +962,6 @@ def write(self, s): self.stdout_hist += t self.echo(s) - self.s_hist.append(s.rstrip()) def push(self, s, insert_into_history=True): # Restore the original SIGINT handler. This is needed to be able @@ -1013,7 +1010,6 @@ def prompt(self, more): self.current_output = None # XXX is this the right place? self.rl_history.reset() - # XXX what is s_hist? # We need the caption to use unicode as urwid normalizes later # input to be the same type, using ascii as encoding. If the @@ -1074,7 +1070,6 @@ def handle_input(self, event): inp = self.edit.get_edit_text() self.history.append(inp) self.edit.make_readonly() - # XXX what is this s_hist thing? if py3: self.stdout_hist += inp else: From afc28c3fb20a23c497bca11a0a6afb1b22bd6c97 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 6 Sep 2020 10:45:33 +0200 Subject: [PATCH 0981/1650] Do not patch __loader__ if not available --- bpython/curtsiesfrontend/repl.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 1eccf2ec6..578fe2b0f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -290,13 +290,14 @@ def find_distributions(self, context): def find_spec(self, fullname, path, target=None): for finder in self.old_meta_path: # Consider the finder only if it implements find_spec - if not getattr(finder, "find_spec", None): + if getattr(finder, "find_spec", None) is None: continue # Attempt to find the spec spec = finder.find_spec(fullname, path, target) if spec is not None: - # Patch the loader to enable reloading - spec.__loader__ = ImportLoader(self.watcher, spec.__loader__) + if getattr(spec, "__loader__", None) is not None: + # Patch the loader to enable reloading + spec.__loader__ = ImportLoader(self.watcher, spec.__loader__) return spec def find_module(self, fullname, path=None): From 46270e068f3775456501c837fec54ec547d6f1b5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 6 Sep 2020 10:48:48 +0200 Subject: [PATCH 0982/1650] Delete _Helper inside the interpreter instance --- bpython/curtsiesfrontend/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 578fe2b0f..f6657f46c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1933,12 +1933,12 @@ def reevaluate(self, new_code=False): def initialize_interp(self): self.coderunner.interp.locals["_repl"] = self self.coderunner.interp.runsource( - "from bpython.curtsiesfrontend._internal " "import _Helper" + "from bpython.curtsiesfrontend._internal import _Helper\n" ) self.coderunner.interp.runsource("help = _Helper(_repl)\n") + self.coderunner.interp.runsource("del _Helper\n") del self.coderunner.interp.locals["_repl"] - del self.coderunner.interp.locals["_Helper"] def getstdout(self): """ From 4ede3895488434414ee81f29eee2dce605b3fbfd Mon Sep 17 00:00:00 2001 From: rybarczykj Date: Fri, 14 Aug 2020 10:17:05 -0500 Subject: [PATCH 0983/1650] Integrate width awareness - update display_linize to use curtsies 3.0 width functionality - correct cursor positioning on wrapped fullwidth chars by adding new method - add test coverage --- bpython/curtsiesfrontend/repl.py | 25 +++++++++++++++++++--- bpython/curtsiesfrontend/replpainter.py | 28 ++++++++++++++----------- bpython/test/test_curtsies_painting.py | 26 +++++++++++++++++++++++ 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f6657f46c..89e18c499 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -403,7 +403,7 @@ def __init__( # so we're just using the same object self.interact = self.status_bar - # line currently being edited, without ps1 (usually '>>> ') + # logical line currently being edited, without ps1 (usually '>>> ') self._current_line = "" # current line of output - stdout and stdin go here @@ -745,8 +745,9 @@ def process_key_event(self, e): ) and self.config.curtsies_right_arrow_completion and self.cursor_offset == len(self.current_line) + # if at end of current line and user presses RIGHT (to autocomplete) ): - + # then autocomplete self.current_line += self.current_suggestion self.cursor_offset = len(self.current_line) elif e in ("",) + key_dispatch[self.config.up_one_line_key]: @@ -1436,6 +1437,23 @@ def current_output_line(self, value): self.current_stdouterr_line = "" self.stdin.current_line = "\n" + def number_of_padding_chars_on_current_cursor_line(self): + """ To avoid cutting off two-column characters at the end of lines where + there's only one column left, curtsies adds a padding char (u' '). + It's important to know about these for cursor positioning. + + Should return zero unless there are fullwidth characters. """ + full_line = self.current_cursor_line_without_suggestion + line_with_padding = "".join( + line.s + for line in paint.display_linize( + self.current_cursor_line_without_suggestion.s, self.width + ) + ) + + # the difference in length here is how much padding there is + return len(line_with_padding) - len(full_line) + def paint( self, about_to_exit=False, @@ -1621,7 +1639,8 @@ def move_screen_up(current_line_start_row): wcswidth(self.current_cursor_line_without_suggestion.s) - wcswidth(self.current_line) + wcswidth(self.current_line[: self.cursor_offset]) - ), + ) + + self.number_of_padding_chars_on_current_cursor_line(), width, ) assert cursor_column >= 0, ( diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index b1fdf3a81..8a4c94b62 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -26,20 +26,24 @@ def display_linize(msg, columns, blank_line=False): """Returns lines obtained by splitting msg over multiple lines. Warning: if msg is empty, returns an empty list of lines""" - display_lines = ( - [ - msg[start:end] - for start, end in zip( - range(0, len(msg), columns), - range(columns, len(msg) + columns, columns), - ) - ] - if msg - else ([""] if blank_line else []) - ) + if not msg: + return [''] if blank_line else [] + msg = fmtstr(msg) + try: + display_lines = list(msg.width_aware_splitlines(columns)) + # use old method if wcwidth can't determine width of msg + except ValueError: + display_lines = ( + [ + msg[start:end] + for start, end in zip( + range(0, len(msg), columns), + range(columns, len(msg) + columns, columns), + ) + ] + ) return display_lines - def paint_history(rows, columns, display_lines): lines = [] for r, line in zip(range(rows), display_lines[-rows:]): diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 15424c662..21d96521e 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -271,6 +271,32 @@ def send_key(self, key): self.repl.process_event("" if key == " " else key) self.repl.paint() # has some side effects we need to be wary of +class TestWidthAwareness(HigherLevelCurtsiesPaintingTest): + + def test_cursor_position_with_fullwidth_char(self): + self.repl.add_normal_character("間") + + cursor_pos = self.repl.paint()[1] + self.assertEqual(cursor_pos, (0,6)) + + def test_cursor_position_with_padding_char(self): + # odd numbered so fullwidth chars don't wrap evenly + self.repl.width = 11 + [self.repl.add_normal_character(c) for c in "width"] + + cursor_pos = self.repl.paint()[1] + self.assertEqual(cursor_pos, (1,4)) + + def test_display_of_padding_chars(self): + self.repl.width = 11 + [self.repl.add_normal_character(c) for c in "width"] + + self.enter() + expected = [ + '>>> wid ', # <--- note the added trailing space + 'th'] + result = [d.s for d in self.repl.display_lines[0:2]] + self.assertEqual(result, expected) class TestCurtsiesRewindRedraw(HigherLevelCurtsiesPaintingTest): def test_rewind(self): From f51c5e6bc9b3269633fac8109e6cf8c781721b5d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 6 Sep 2020 16:28:06 +0200 Subject: [PATCH 0984/1650] Implement a non-configurable skiplist for import completion (fixes #845) --- bpython/importcompletion.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 745a4cea6..86fd73dc1 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -32,6 +32,7 @@ current_from_import_import, ) +import fnmatch import os import sys import warnings @@ -51,6 +52,9 @@ # List of stored paths to compare against so that real paths are not repeated # handles symlinks not mount points paths = set() +# Patterns to skip +# TODO: This skiplist should be configurable. +skiplist = (".git", ".config", ".local", ".share", "node_modules") fully_loaded = False @@ -140,6 +144,10 @@ def find_modules(path): if not os.path.isdir(path): # Perhaps a zip file return + basepath = os.path.basename(path) + if any(fnmatch.fnmatch(basepath, entry) for entry in skiplist): + # Path is on skiplist + return try: filenames = os.listdir(path) @@ -150,7 +158,10 @@ def find_modules(path): finder = importlib.machinery.FileFinder(path) for name in filenames: - if not any(name.endswith(suffix) for suffix in SUFFIXES): + if any(fnmatch.fnmatch(name, entry) for entry in skiplist): + # Path is on skiplist + continue + elif not any(name.endswith(suffix) for suffix in SUFFIXES): # Possibly a package if "." in name: continue From 99c601a7ce8800bd2af06ffee7d0fd5831c46d32 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 11:28:22 +0200 Subject: [PATCH 0985/1650] Support set nodes in simple_eval Merges changes from Python 3.9's ast.literal_eval. --- bpython/simpleeval.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 98ce41512..2f239320a 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -81,7 +81,7 @@ def simple_eval(node_or_string, namespace=None): The string or node provided may only consist of: * the following Python literal structures: strings, numbers, tuples, - lists, and dicts + lists, dicts, sets (Python 3.9+) * variable names causing lookups in the passed in namespace or builtins * getitem calls using the [] syntax on objects of the types above @@ -113,6 +113,8 @@ def _convert(node): (_convert(k), _convert(v)) for k, v in zip(node.keys, node.values) ) + elif sys.version_info[:2] >= (3, 9) and isinstance(node, ast.Set): + return set(map(_convert, node.elts)) # this is a deviation from literal_eval: we allow non-literals elif isinstance(node, _name_type_nodes): From baa92a09e8285981823f013bc0fafa6e189a25f8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 11:52:52 +0200 Subject: [PATCH 0986/1650] Handle Subscript nodes with slices of type ast.Constant This partly fixes #809 --- bpython/simpleeval.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 2f239320a..5234143e1 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -160,6 +160,12 @@ def _convert(node): obj = _convert(node.value) index = _convert(node.slice.value) return safe_getitem(obj, index) + elif sys.version_info[:2] >= (3, 9) and isinstance(node, ast.Subscript) and isinstance( + node.slice, ast.Constant + ): + obj = _convert(node.value) + index = node.slice.value + return safe_getitem(obj, index) # this is a deviation from literal_eval: we allow attribute access if isinstance(node, ast.Attribute): From 900cf061e492e65fd9ec3cb1a61ac74b4e649efd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 13:02:20 +0200 Subject: [PATCH 0987/1650] Also handle Name nodes for indexing (fixes #809) --- bpython/simpleeval.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 5234143e1..9b3aeddb9 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -161,10 +161,10 @@ def _convert(node): index = _convert(node.slice.value) return safe_getitem(obj, index) elif sys.version_info[:2] >= (3, 9) and isinstance(node, ast.Subscript) and isinstance( - node.slice, ast.Constant + node.slice, (ast.Constant, ast.Name) ): obj = _convert(node.value) - index = node.slice.value + index = _convert(node.slice) return safe_getitem(obj, index) # this is a deviation from literal_eval: we allow attribute access From 6f810abf519fd6d6fd448a70801f237b5bfc34d9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 13:52:49 +0200 Subject: [PATCH 0988/1650] travis: enable 3.9 and make it fatal --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5973aa401..0167d7b93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: - "3.6" - "3.7" - "3.8" - - "3.9-dev" + - "3.9" - "pypy" - "pypy3" @@ -18,7 +18,6 @@ env: matrix: allow_failures: - - python: "3.9-dev" - python: "pypy" - python: "pypy3" From a546f4694e2810ddcd3f0fe08f092e2225677c9d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 14:38:10 +0200 Subject: [PATCH 0989/1650] travis: bump dist to bionic --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0167d7b93..883248803 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: python sudo: false -dist: xenial +dist: bionic python: - "2.7" From 723247f94d6f6445f510d491d4f676c490ef0ef4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 14:38:32 +0200 Subject: [PATCH 0990/1650] travis: Python 3.4 reached EOL --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 883248803..49e6c6b99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ dist: bionic python: - "2.7" - - "3.5" - "3.6" - "3.7" - "3.8" From 6fcc76828e95aa9b0a580f08130383ec26878efa Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 14:58:27 +0200 Subject: [PATCH 0991/1650] Update changelog --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c08c5c2aa..49b8b2959 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,15 +7,20 @@ Changelog New features: * #802: Provide redo. Thanks to Evan. +* #835: Add support for importing namespace packages. + Thanks to Thomas Babej. Fixes: * #806: Prevent symbolic link loops in import completion. Thanks to Etienne Richart. * #807: Support packages using importlib.metadata API. Thanks to uriariel. +* #809: Fix support for Python 3.9's ast module. * #817: Fix cursor position with full-width characters. Thanks to Jack Rybarczyk. +Support for Python 3.9 has been added. Support for Python 3.5 has been dropped. + 0.19 ---- From 660cd66d49310708277b4ef10a248b24e0ea3770 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 15:03:24 +0200 Subject: [PATCH 0992/1650] Simplify version checks --- bpython/simpleeval.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 9b3aeddb9..c1349d683 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -42,12 +42,7 @@ _string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) _numeric_types = (int, float, complex) + (() if py3 else (long,)) - -# added in Python 3.4 -if hasattr(ast, "NameConstant"): - _name_type_nodes = (ast.Name, ast.NameConstant) -else: - _name_type_nodes = (ast.Name,) +_name_type_nodes = (ast.Name, ast.NameConstant) if py3 else (ast.Name,) class EvaluationError(Exception): @@ -98,7 +93,7 @@ def simple_eval(node_or_string, namespace=None): node_or_string = node_or_string.body def _convert(node): - if sys.version_info[:2] >= (3, 6) and isinstance(node, ast.Constant): + if py3 and isinstance(node, ast.Constant): return node.value if isinstance(node, _string_type_nodes): return node.s From 30d85623933565613ae9aa7c3170dd435670bf46 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 15:04:34 +0200 Subject: [PATCH 0993/1650] Update supported Python versions --- doc/sphinx/source/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 430aabc6e..f97157342 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -17,7 +17,7 @@ the time of day. Getting your development environment set up ------------------------------------------- -bpython supports Python 2.7, 3.4 and newer. The code is compatible with all +bpython supports Python 2.7, 3.6 and newer. The code is compatible with all supported versions without the need to run post processing like `2to3`. Using a virtual environment is probably a good idea. Create a virtual From 8b2c97f954931346826e94a5905822cc6387dc4d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 15:05:58 +0200 Subject: [PATCH 0994/1650] Apply black --- bpython/autocomplete.py | 2 +- bpython/curtsiesfrontend/repl.py | 8 ++++++-- bpython/curtsiesfrontend/replpainter.py | 19 +++++++++---------- bpython/repl.py | 1 + bpython/simpleeval.py | 6 ++++-- bpython/test/test_args.py | 3 +-- bpython/test/test_autocomplete.py | 10 +++++----- bpython/test/test_curtsies_painting.py | 17 ++++++++--------- bpython/test/test_import_not_cyclical.py | 4 +++- bpython/test/test_inspection.py | 17 +++++++++++------ bpython/test/test_simpleeval.py | 2 -- 11 files changed, 49 insertions(+), 40 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 067a395f6..a3c61ea9d 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -537,7 +537,7 @@ def matches(self, cursor_offset, line, **kwargs): except EvaluationError: return set() - # strips leading dot + # strips leading dot matches = [m[1:] for m in self.attr_lookup(obj, "", attr.word)] return set(m for m in matches if few_enough_underscores(attr.word, m)) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 89e18c499..6f0cec540 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -297,7 +297,9 @@ def find_spec(self, fullname, path, target=None): if spec is not None: if getattr(spec, "__loader__", None) is not None: # Patch the loader to enable reloading - spec.__loader__ = ImportLoader(self.watcher, spec.__loader__) + spec.__loader__ = ImportLoader( + self.watcher, spec.__loader__ + ) return spec def find_module(self, fullname, path=None): @@ -1040,7 +1042,9 @@ def send_session_to_external_editor(self, filename=None): current_line = lines[-1][4:] else: current_line = "" - from_editor = [line for line in lines if line[:6] != "# OUT:" and line[:3] != "###"] + from_editor = [ + line for line in lines if line[:6] != "# OUT:" and line[:3] != "###" + ] if all(not line.strip() for line in from_editor): self.status_bar.message( _("Session not reevaluated because saved file was blank") diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 8a4c94b62..73676fb69 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -27,23 +27,22 @@ def display_linize(msg, columns, blank_line=False): Warning: if msg is empty, returns an empty list of lines""" if not msg: - return [''] if blank_line else [] + return [""] if blank_line else [] msg = fmtstr(msg) try: display_lines = list(msg.width_aware_splitlines(columns)) # use old method if wcwidth can't determine width of msg except ValueError: - display_lines = ( - [ - msg[start:end] - for start, end in zip( - range(0, len(msg), columns), - range(columns, len(msg) + columns, columns), - ) - ] - ) + display_lines = [ + msg[start:end] + for start, end in zip( + range(0, len(msg), columns), + range(columns, len(msg) + columns, columns), + ) + ] return display_lines + def paint_history(rows, columns, display_lines): lines = [] for r, line in zip(range(rows), display_lines[-rows:]): diff --git a/bpython/repl.py b/bpython/repl.py index 353aafe1d..0ac22f83d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -391,6 +391,7 @@ class SourceNotFound(Exception): class LineTypeTranslator(object): """ Used when adding a tuple to all_logical_lines, to get input / output values having to actually type/know the strings """ + # TODO use Enum once we drop support for Python 2 INPUT = "input" diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index c1349d683..8d656162c 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -155,8 +155,10 @@ def _convert(node): obj = _convert(node.value) index = _convert(node.slice.value) return safe_getitem(obj, index) - elif sys.version_info[:2] >= (3, 9) and isinstance(node, ast.Subscript) and isinstance( - node.slice, (ast.Constant, ast.Name) + elif ( + sys.version_info[:2] >= (3, 9) + and isinstance(node, ast.Subscript) + and isinstance(node.slice, (ast.Constant, ast.Name)) ): obj = _convert(node.value) index = _convert(node.slice) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index e9c67e25b..820f790df 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -34,8 +34,7 @@ def test_exec_dunder_file(self): ) f.flush() p = subprocess.Popen( - [sys.executable] - + ["-m", "bpython.curtsies", f.name], + [sys.executable] + ["-m", "bpython.curtsies", f.name], stderr=subprocess.PIPE, universal_newlines=True, ) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 3cc331ef1..4cbbf628a 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -251,10 +251,12 @@ def asserts_when_called(self): class Slots(object): __slots__ = ["a", "b"] + class OverriddenGetattribute(Foo): - def __getattribute__(self,name): + def __getattribute__(self, name): raise AssertionError("custom get attribute invoked") + class TestAttrCompletion(unittest.TestCase): @classmethod def setUpClass(cls): @@ -301,7 +303,7 @@ def test_descriptor_attributes_not_run(self): com.matches(2, "a.", locals_={"a": Properties()}), set(["a.b", "a.a", "a.method", "a.asserts_when_called"]), ) - + def test_custom_get_attribute_not_invoked(self): com = autocomplete.AttrCompletion() self.assertSetEqual( @@ -309,12 +311,10 @@ def test_custom_get_attribute_not_invoked(self): set(["a.b", "a.a", "a.method"]), ) - def test_slots_not_crash(self): com = autocomplete.AttrCompletion() self.assertSetEqual( - com.matches(2, "A.", locals_={"A": Slots}), - set(["A.b", "A.a"]), + com.matches(2, "A.", locals_={"A": Slots}), set(["A.b", "A.a"]), ) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 21d96521e..64a46b15c 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -271,32 +271,31 @@ def send_key(self, key): self.repl.process_event("" if key == " " else key) self.repl.paint() # has some side effects we need to be wary of -class TestWidthAwareness(HigherLevelCurtsiesPaintingTest): +class TestWidthAwareness(HigherLevelCurtsiesPaintingTest): def test_cursor_position_with_fullwidth_char(self): self.repl.add_normal_character("間") cursor_pos = self.repl.paint()[1] - self.assertEqual(cursor_pos, (0,6)) + self.assertEqual(cursor_pos, (0, 6)) def test_cursor_position_with_padding_char(self): # odd numbered so fullwidth chars don't wrap evenly - self.repl.width = 11 + self.repl.width = 11 [self.repl.add_normal_character(c) for c in "width"] cursor_pos = self.repl.paint()[1] - self.assertEqual(cursor_pos, (1,4)) + self.assertEqual(cursor_pos, (1, 4)) def test_display_of_padding_chars(self): - self.repl.width = 11 + self.repl.width = 11 [self.repl.add_normal_character(c) for c in "width"] self.enter() - expected = [ - '>>> wid ', # <--- note the added trailing space - 'th'] + expected = [">>> wid ", "th"] # <--- note the added trailing space result = [d.s for d in self.repl.display_lines[0:2]] - self.assertEqual(result, expected) + self.assertEqual(result, expected) + class TestCurtsiesRewindRedraw(HigherLevelCurtsiesPaintingTest): def test_rewind(self): diff --git a/bpython/test/test_import_not_cyclical.py b/bpython/test/test_import_not_cyclical.py index e1c0b045e..6b9581853 100644 --- a/bpython/test/test_import_not_cyclical.py +++ b/bpython/test/test_import_not_cyclical.py @@ -67,7 +67,9 @@ def setUp(self): True, ) - self.modules = list(find_modules(os.path.abspath(import_test_folder))) + self.modules = list( + find_modules(os.path.abspath(import_test_folder)) + ) self.filepaths = [ "Left.toRight.toLeft", "Left.toRight", diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 08dda4b6c..ecb2a6e9f 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -204,9 +204,11 @@ def __mro__(self): raise AssertionError("custom mro executed") a = 1 - + + member_descriptor = type(Slots.s1) + class TestSafeGetAttribute(unittest.TestCase): def test_lookup_on_object(self): a = A() @@ -222,12 +224,11 @@ def test_lookup_on_object(self): self.assertEqual(inspection.hasattr_safe(b, "y"), True) self.assertEqual(inspection.hasattr_safe(b, "b"), True) - def test_avoid_running_properties(self): p = Property() self.assertEqual(inspection.getattr_safe(p, "prop"), Property.prop) self.assertEqual(inspection.hasattr_safe(p, "prop"), True) - + def test_lookup_with_slots(self): s = Slots() s.s1 = "s1" @@ -269,10 +270,14 @@ def test_lookup_on_overridden_methods(self): with self.assertRaises(AttributeError): sga(OverriddenMRO(), "b") - self.assertEqual(inspection.hasattr_safe(OverriddenGetattr(), "b"), False) - self.assertEqual(inspection.hasattr_safe(OverriddenGetattribute(), "b"), False) + self.assertEqual( + inspection.hasattr_safe(OverriddenGetattr(), "b"), False + ) + self.assertEqual( + inspection.hasattr_safe(OverriddenGetattribute(), "b"), False + ) self.assertEqual(inspection.hasattr_safe(OverriddenMRO(), "b"), False) - + if __name__ == "__main__": unittest.main() diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 436517c4b..7752c5926 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -133,7 +133,5 @@ def test_with_namespace(self): self.assertCannotEval("a[1].a|bc", {}) - - if __name__ == "__main__": unittest.main() From d5183ff00eb2d6d4f25af41b657e248ced7b10af Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 15:08:54 +0200 Subject: [PATCH 0995/1650] travis: Also test with nightly Python builds --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 49e6c6b99..c28c9cb3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,12 @@ sudo: false dist: bionic python: - - "2.7" - - "3.6" - - "3.7" + - "3.9-dev" - "3.8" - - "3.9" + - "3.7" + - "3.6" + - "2.7" + - "nightly" - "pypy" - "pypy3" @@ -17,6 +18,7 @@ env: matrix: allow_failures: + - python: "nightly" - python: "pypy" - python: "pypy3" From f3cf6a6728394b3c3ca865ee0e506194533f2cb1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 15:25:41 +0200 Subject: [PATCH 0996/1650] Use py3 for version checks --- bpython/cli.py | 5 ++--- bpython/curtsies.py | 3 ++- bpython/urwid.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index dc3ebc829..193182dcb 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -64,7 +64,7 @@ # These are used for syntax highlighting from pygments import format from pygments.formatters import TerminalFormatter -from ._py3compat import PythonLexer +from ._py3compat import PythonLexer, py3 from pygments.token import Token from .formatter import BPythonFormatter @@ -83,7 +83,6 @@ from . import repl from . import args as bpargs -from ._py3compat import py3 from .pager import page from .args import parse as argsparse @@ -1998,7 +1997,7 @@ def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): ) clirepl.write("\n") - if sys.version_info[0] == 2: + if not py3: # XXX these deprecation warnings need to go at some point clirepl.write( _( diff --git a/bpython/curtsies.py b/bpython/curtsies.py index b5921a109..24ceb6927 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -23,6 +23,7 @@ from .curtsiesfrontend import events as bpythonevents from . import inspection from .repl import extract_exit_value +from ._py3compat import py3 logger = logging.getLogger(__name__) @@ -202,7 +203,7 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): if banner is not None: print(banner) - if sys.version_info[0] == 2: + if not py3: # XXX these deprecation warnings need to go at some point print( _( diff --git a/bpython/urwid.py b/bpython/urwid.py index b33dc1f22..e0260873a 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1394,7 +1394,7 @@ def start(main_loop, user_data): ) myrepl.write("\n") - if sys.version_info[0] == 2: + if not py3: # XXX these deprecation warnings need to go at some point myrepl.write( _( From 19641c3911f7e0db93787bc0c50e1ff9ef28f920 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 16:04:59 +0200 Subject: [PATCH 0997/1650] Add simpleeval tests for set literals --- bpython/test/test_simpleeval.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 7752c5926..232ad3243 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -20,6 +20,7 @@ def test_matches_stdlib(self): """Should match the stdlib literal_eval if no names or indexing""" self.assertMatchesStdlib("[1]") self.assertMatchesStdlib("{(1,): [2,3,{}]}") + self.assertMatchesStdlib("{1, 2}") def test_indexing(self): """Literals can be indexed into""" From 35c7c687c5846bc0458722fe6e9f150586da625a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 16:05:19 +0200 Subject: [PATCH 0998/1650] Handle set literals in all versions --- bpython/simpleeval.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 8d656162c..738c67cdf 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -76,7 +76,7 @@ def simple_eval(node_or_string, namespace=None): The string or node provided may only consist of: * the following Python literal structures: strings, numbers, tuples, - lists, dicts, sets (Python 3.9+) + lists, dicts, and sets * variable names causing lookups in the passed in namespace or builtins * getitem calls using the [] syntax on objects of the types above @@ -108,7 +108,7 @@ def _convert(node): (_convert(k), _convert(v)) for k, v in zip(node.keys, node.values) ) - elif sys.version_info[:2] >= (3, 9) and isinstance(node, ast.Set): + elif isinstance(node, ast.Set): return set(map(_convert, node.elts)) # this is a deviation from literal_eval: we allow non-literals From 30f30eedb3aa320843e3978ea973759fe371b6af Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 16:11:08 +0200 Subject: [PATCH 0999/1650] Refactor simple_eval tests --- bpython/test/test_simpleeval.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 232ad3243..f1c66d739 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -25,17 +25,15 @@ def test_matches_stdlib(self): def test_indexing(self): """Literals can be indexed into""" self.assertEqual(simple_eval("[1,2][0]"), 1) - self.assertEqual(simple_eval("a", {"a": 1}), 1) def test_name_lookup(self): - """Names can be lookup up in a namespace""" + """Names can be looked up in a namespace""" self.assertEqual(simple_eval("a", {"a": 1}), 1) self.assertEqual(simple_eval("map"), map) - self.assertEqual(simple_eval("a[b]", {"a": {"c": 1}, "b": "c"}), 1) - def test_allow_name_lookup(self): - """Names can be lookup up in a namespace""" - self.assertEqual(simple_eval("a", {"a": 1}), 1) + def test_name_lookup_indexing(self): + """Names can be looked up in a namespace""" + self.assertEqual(simple_eval("a[b]", {"a": {"c": 1}, "b": "c"}), 1) def test_lookup_on_suspicious_types(self): class FakeDict(object): From 13d467132216dc7a381eae63f2e2345756f65cb7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 16:18:08 +0200 Subject: [PATCH 1000/1650] Skip tests for set literals under Python 2 --- bpython/test/test_simpleeval.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index f1c66d739..5fb5a525f 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -20,6 +20,10 @@ def test_matches_stdlib(self): """Should match the stdlib literal_eval if no names or indexing""" self.assertMatchesStdlib("[1]") self.assertMatchesStdlib("{(1,): [2,3,{}]}") + + @unittest.skipUnless(py3, "Only Python3 versions of ast.literal_eval evaluate set literals") + def test_matches_stdlib_py3(self): + """Should match the stdlib literal_eval if no names or indexing""" self.assertMatchesStdlib("{1, 2}") def test_indexing(self): From 9b7009f2545975f9e709c9fb8fa63a95c9800028 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 16:44:24 +0200 Subject: [PATCH 1001/1650] Handle set() as literal as in ast.literal_eval --- bpython/simpleeval.py | 10 ++++++++-- bpython/test/test_simpleeval.py | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 738c67cdf..262073e8e 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -95,7 +95,7 @@ def simple_eval(node_or_string, namespace=None): def _convert(node): if py3 and isinstance(node, ast.Constant): return node.value - if isinstance(node, _string_type_nodes): + elif isinstance(node, _string_type_nodes): return node.s elif isinstance(node, ast.Num): return node.n @@ -110,6 +110,13 @@ def _convert(node): ) elif isinstance(node, ast.Set): return set(map(_convert, node.elts)) + elif ( + isinstance(node, Call) + and isinstance(node.func, Name) + and node.func.id == "set" + and node.args == node.keywords == [] + ): + return set() # this is a deviation from literal_eval: we allow non-literals elif isinstance(node, _name_type_nodes): @@ -256,4 +263,3 @@ def evaluate_current_attribute(cursor_offset, line, namespace=None): raise EvaluationError( "can't lookup attribute %s on %r" % (attr.word, obj) ) - diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 5fb5a525f..8ea12bf3e 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -25,6 +25,7 @@ def test_matches_stdlib(self): def test_matches_stdlib_py3(self): """Should match the stdlib literal_eval if no names or indexing""" self.assertMatchesStdlib("{1, 2}") + self.assertMatchesStdlib("set()") def test_indexing(self): """Literals can be indexed into""" From 5b2634e3375ca9b4405a3be8ea189e0a1d771c32 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 16:57:00 +0200 Subject: [PATCH 1002/1650] Fix handling of set() --- bpython/simpleeval.py | 4 ++-- bpython/test/test_simpleeval.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 262073e8e..73b623fbc 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -111,8 +111,8 @@ def _convert(node): elif isinstance(node, ast.Set): return set(map(_convert, node.elts)) elif ( - isinstance(node, Call) - and isinstance(node.func, Name) + isinstance(node, ast.Call) + and isinstance(node.func, ast.Name) and node.func.id == "set" and node.args == node.keywords == [] ): diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 8ea12bf3e..2e12fcb4d 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -2,6 +2,7 @@ import ast import numbers +import sys from bpython.simpleeval import ( simple_eval, @@ -25,6 +26,10 @@ def test_matches_stdlib(self): def test_matches_stdlib_py3(self): """Should match the stdlib literal_eval if no names or indexing""" self.assertMatchesStdlib("{1, 2}") + + @unittest.skipUnless(sys.version_info[:2] >= (3, 9), "Only Python3.9 evaluates set()") + def test_matches_stdlib_set_literal(self): + """set() is evaluated""" self.assertMatchesStdlib("set()") def test_indexing(self): From 5bdb173a6fb778d27e152c334e47c4a55234c858 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 17:42:44 +0200 Subject: [PATCH 1003/1650] Raise ValueError with repr of failing node --- bpython/simpleeval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 73b623fbc..c6a46ba35 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -177,7 +177,7 @@ def _convert(node): attr = node.attr return getattr_safe(obj, attr) - raise ValueError("malformed string") + raise ValueError("malformed node or string: {!r}".format(node)) return _convert(node_or_string) From e59905b1eb4f8cea54b9bbefd656e2fe798e5e40 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 11 Oct 2020 14:21:26 -0700 Subject: [PATCH 1004/1650] Fix fd encodings (#629) * Use real stdin encoding for fake stdin encoding. this is both more accurate and corrects the spelling from UTF8 to UTF-8 --- bpython/curtsiesfrontend/coderunner.py | 10 +++++++--- bpython/curtsiesfrontend/repl.py | 6 +++--- bpython/test/test_curtsies_coderunner.py | 10 +++++----- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 6087edb08..d53e75d48 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -205,7 +205,7 @@ def request_from_main_context(self, force_refresh=False): class FakeOutput(object): - def __init__(self, coderunner, on_write, fileno=1): + def __init__(self, coderunner, on_write, real_fileobj): """Fakes sys.stdout or sys.stderr on_write should always take unicode @@ -215,7 +215,7 @@ def __init__(self, coderunner, on_write, fileno=1): """ self.coderunner = coderunner self.on_write = on_write - self.real_fileno = fileno + self._real_fileobj = real_fileobj def write(self, s, *args, **kwargs): if not py3 and isinstance(s, str): @@ -227,7 +227,7 @@ def write(self, s, *args, **kwargs): # have a method called fileno. One example is pwntools. This # is not a widespread issue, but is annoying. def fileno(self): - return self.real_fileno + return self._real_fileobj.fileno() def writelines(self, l): for s in l: @@ -238,3 +238,7 @@ def flush(self): def isatty(self): return True + + @property + def encoding(self): + return self._real_fileobj.encoding diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 6f0cec540..a899bcc94 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -223,7 +223,7 @@ def close(self): @property def encoding(self): - return "UTF8" + return sys.__stdin__.encoding # TODO write a read() method? @@ -447,12 +447,12 @@ def __init__( self.stdout = FakeOutput( self.coderunner, self.send_to_stdouterr, - fileno=sys.__stdout__.fileno(), + real_fileobj=sys.__stdout__, ) self.stderr = FakeOutput( self.coderunner, self.send_to_stdouterr, - fileno=sys.__stderr__.fileno(), + real_fileobj=sys.__stderr__, ) self.stdin = FakeStdin(self.coderunner, self, self.edit_keys) diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index 25844af59..94d8106d6 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -20,8 +20,8 @@ def test_simple(self): request_refresh=lambda: self.orig_stdout.flush() or self.orig_stderr.flush() ) - stdout = FakeOutput(c, lambda *args, **kwargs: None) - stderr = FakeOutput(c, lambda *args, **kwargs: None) + stdout = FakeOutput(c, lambda *args, **kwargs: None, None) + stderr = FakeOutput(c, lambda *args, **kwargs: None, None) sys.stdout = stdout sys.stdout = stderr c.load_code("1 + 1") @@ -38,8 +38,8 @@ def test_exception(self): def ctrlc(): raise KeyboardInterrupt() - stdout = FakeOutput(c, lambda x: ctrlc()) - stderr = FakeOutput(c, lambda *args, **kwargs: None) + stdout = FakeOutput(c, lambda x: ctrlc(), None) + stderr = FakeOutput(c, lambda *args, **kwargs: None, None) sys.stdout = stdout sys.stderr = stderr c.load_code("1 + 1") @@ -51,5 +51,5 @@ def assert_unicode(self, s): self.assertIsInstance(s, type(u"")) def test_bytes(self): - out = FakeOutput(mock.Mock(), self.assert_unicode) + out = FakeOutput(mock.Mock(), self.assert_unicode, None) out.write("native string type") From d632b51c3ec3e0fef059ff2259963424056b5cb5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 11 Oct 2020 23:31:25 +0200 Subject: [PATCH 1005/1650] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 49b8b2959..5a9cfb2ed 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ New features: Thanks to Thomas Babej. Fixes: +* #622: Provide encoding attribute for FakeOutput. * #806: Prevent symbolic link loops in import completion. Thanks to Etienne Richart. * #807: Support packages using importlib.metadata API. From cd01afdd9ba218727f77b059e4dad3d0b4622f93 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 11 Oct 2020 09:57:24 -0700 Subject: [PATCH 1006/1650] Restore original save behavior for cli and urwid --- bpython/curtsiesfrontend/repl.py | 2 ++ bpython/repl.py | 35 ++++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index a899bcc94..351a883b2 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1164,6 +1164,8 @@ def push(self, line, insert_into_history=True): If the interpreter successfully runs the code, clear the buffer """ + # Note that push() overrides its parent without calling it, unlike + # urwid and cli which implement custom behavior and call repl.Repl.push if self.paste_mode: self.saved_indent = 0 else: diff --git a/bpython/repl.py b/bpython/repl.py index 0ac22f83d..fd31edbbb 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -832,15 +832,31 @@ def get_session_formatted_for_file(self): i.e. without >>> and ... at input lines and with "# OUT: " prepended to output lines and "### " prepended to current line""" - def process(): - for line, lineType in self.all_logical_lines: - if lineType == LineTypeTranslator.INPUT: - yield line - elif line.rstrip(): - yield "# OUT: %s" % line - yield "### %s" % self.current_line - - return "\n".join(process()) + if hasattr(self, 'all_logical_lines'): + # Curtsies + + def process(): + for line, lineType in self.all_logical_lines: + if lineType == LineTypeTranslator.INPUT: + yield line + elif line.rstrip(): + yield "# OUT: %s" % line + yield "### %s" % self.current_line + + return "\n".join(process()) + + else: # cli and Urwid + session_output = self.getstdout() + + def process(): + for line in session_output.split("\n"): + if line.startswith(self.ps1): + yield line[len(self.ps1) :] + elif line.startswith(self.ps2): + yield line[len(self.ps2) :] + elif line.rstrip(): + yield "# OUT: %s" % (line,) + return '\n'.join(process()) def write2file(self): """Prompt for a filename and write the current contents of the stdout @@ -952,6 +968,7 @@ def do_pastebin(self, s): def push(self, s, insert_into_history=True): """Push a line of code onto the buffer so it can process it all at once when a code block ends""" + # This push method is used by cli and urwid, but not curtsies s = s.rstrip("\n") self.buffer.append(s) From c9a8aa3829090a9c2133cf991ffa36a51b06270a Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 11 Oct 2020 09:26:25 -0700 Subject: [PATCH 1007/1650] Fix invalid escape sequences. --- bpython/cli.py | 4 ++-- bpython/line.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 193182dcb..68d7f8566 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -939,11 +939,11 @@ def p_key(self, key): # Redraw (as there might have been highlighted parens) self.print_line(self.s) - elif key in ("KEY_NPAGE", "\T"): # page_down or \T + elif key in ("KEY_NPAGE"): # page_down self.hend() self.print_line(self.s) - elif key in ("KEY_PPAGE", "\S"): # page_up or \S + elif key in ("KEY_PPAGE"): # page_up self.hbegin() self.print_line(self.s) diff --git a/bpython/line.py b/bpython/line.py index af54ab31b..25ecc120e 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -190,7 +190,7 @@ def current_import(cursor_offset, line): return LinePart(start, end, m.group(1)) -current_method_definition_name_re = LazyReCompile("def\s+([a-zA-Z_][\w]*)") +current_method_definition_name_re = LazyReCompile(r"def\s+([a-zA-Z_][\w]*)") def current_method_definition_name(cursor_offset, line): From a9fa4cebe4512323a4f3d58d4025daa848288c95 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 12 Oct 2020 22:07:17 +0200 Subject: [PATCH 1008/1650] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 5a9cfb2ed..91e6b9d4c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ Fixes: * #809: Fix support for Python 3.9's ast module. * #817: Fix cursor position with full-width characters. Thanks to Jack Rybarczyk. +* #853: Fix invalid escape sequences. Support for Python 3.9 has been added. Support for Python 3.5 has been dropped. From 403bcb7eafa106ecb080914052b0185507a8c179 Mon Sep 17 00:00:00 2001 From: supakeen Date: Tue, 13 Oct 2020 17:41:01 +0200 Subject: [PATCH 1009/1650] Clarify Python 2 support droppage in 0.20. --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 91e6b9d4c..0e8fd2c97 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ Fixes: * #853: Fix invalid escape sequences. Support for Python 3.9 has been added. Support for Python 3.5 has been dropped. +Version 0.21 (the next release of bpython) will drop support for Python 2.x. 0.19 ---- From 5d6e6f254c05f352b01dd802628c9f606fd735c4 Mon Sep 17 00:00:00 2001 From: supakeen Date: Tue, 13 Oct 2020 17:52:12 +0200 Subject: [PATCH 1010/1650] Move up deprecations in CHANGELOG. Deprecations are generally important information and should be read first. --- CHANGELOG | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0e8fd2c97..3606bbadb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,12 @@ Changelog 0.20 ---- +General information: + +* The next release of bpython (0.20) will drop support for Python 2. +* Support for Python 3.9 has been added. Support for Python 3.5 has been + dropped. + New features: * #802: Provide redo. Thanks to Evan. @@ -21,9 +27,6 @@ Fixes: Thanks to Jack Rybarczyk. * #853: Fix invalid escape sequences. -Support for Python 3.9 has been added. Support for Python 3.5 has been dropped. -Version 0.21 (the next release of bpython) will drop support for Python 2.x. - 0.19 ---- From 1659b6d7461ff81f2972372980cc0f9a0427a419 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 20:35:29 +0200 Subject: [PATCH 1011/1650] Start development of 0.21 --- CHANGELOG | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 3606bbadb..e380b2714 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,17 @@ Changelog ========= +0.21 +---- + +General information: + +* Support for Python 2 has been dropped. + +New features: + +Fixes: + 0.20 ---- From d20869f174a7e639ab5fdd3080b45f315fc07469 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 13:08:07 +0200 Subject: [PATCH 1012/1650] travis: no longer test Python 2 and PyPy --- .travis.install.sh | 11 ----------- .travis.yml | 3 --- 2 files changed, 14 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index deaf5714a..340048d87 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -13,17 +13,6 @@ if [[ $RUN == nosetests ]]; then pip install jedi # translation specific dependencies pip install babel - # Python 2.7 specific dependencies - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then - # dependencies for crasher tests - pip install Twisted urwid - fi - case $TRAVIS_PYTHON_VERSION in - 2*|pypy) - # test specific dependencies - pip install mock - ;; - esac # build and install python setup.py install elif [[ $RUN == build_sphinx ]]; then diff --git a/.travis.yml b/.travis.yml index c28c9cb3b..04b7750f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,7 @@ python: - "3.8" - "3.7" - "3.6" - - "2.7" - "nightly" - - "pypy" - "pypy3" env: @@ -19,7 +17,6 @@ env: matrix: allow_failures: - python: "nightly" - - python: "pypy" - python: "pypy3" install: From 4e382a8b83a51fb4c9d4d3a9d3d8c6e3402cb4fc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 21:17:24 +0200 Subject: [PATCH 1013/1650] Fix check when matching modules Regression introduced in a194976998c9eeac0f9e71fa14bca57a909a4237. --- bpython/importcompletion.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 86fd73dc1..bde59cf68 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -83,11 +83,8 @@ def attr_matches(cw, prefix="", only_modules=False): matches = ( name for name in dir(module) - if ( - name.startswith(name_after_dot) - and "%s.%s" % (module_name, name) - ) - in sys.modules + if name.startswith(name_after_dot) + and "%s.%s" % (module_name, name) in sys.modules ) else: matches = ( From c0c2e480af6de627db4ccc609c459324f84e0b40 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 13:09:23 +0200 Subject: [PATCH 1014/1650] Remove Python 2-specfic dependencies --- setup.py | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/setup.py b/setup.py index 33f650a1c..166294865 100755 --- a/setup.py +++ b/setup.py @@ -91,8 +91,7 @@ def git_describe_to_python_version(version): stderr=subprocess.PIPE, ) stdout = proc.communicate()[0].strip() - if sys.version_info[0] > 2: - stdout = stdout.decode("ascii") + stdout = stdout.decode("ascii") if proc.returncode == 0: version = git_describe_to_python_version(stdout) @@ -235,18 +234,6 @@ def initialize_options(self): "urwid": ["urwid"], "watch": ["watchdog"], "jedi": ["jedi"], - # need requests[security] for SNI support (only before 2.7.7) - ':python_full_version == "2.7.0" or ' - 'python_full_version == "2.7.1" or ' - 'python_full_version == "2.7.2" or ' - 'python_full_version == "2.7.3" or ' - 'python_full_version == "2.7.4" or ' - 'python_full_version == "2.7.5" or ' - 'python_full_version == "2.7.6"': [ - "pyOpenSSL", - "pyasn1", - "ndg-httpsclient", - ], } packages = [ @@ -267,10 +254,6 @@ def initialize_options(self): ] } -tests_require = [] -if sys.version_info[0] == 2: - tests_require.append("mock") - # translations mo_files = [] for language in os.listdir(translations_dir): @@ -291,7 +274,6 @@ def initialize_options(self): classifiers=classifiers, install_requires=install_requires, extras_require=extras_require, - tests_require=tests_require, packages=packages, data_files=data_files, package_data={ From bebcdb3dc55db4bd9b7a20dff53b23b3324d269e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 13:17:25 +0200 Subject: [PATCH 1015/1650] Remove __future__ imports --- bpdb/__init__.py | 2 -- bpdb/__main__.py | 2 -- bpdb/debugger.py | 2 -- bpython/__init__.py | 1 - bpython/__main__.py | 1 - bpython/_internal.py | 2 -- bpython/_py3compat.py | 1 - bpython/args.py | 1 - bpython/autocomplete.py | 1 - bpython/cli.py | 1 - bpython/clipboard.py | 1 - bpython/config.py | 2 -- bpython/curtsies.py | 2 -- bpython/curtsiesfrontend/interaction.py | 2 -- bpython/curtsiesfrontend/parse.py | 1 - bpython/curtsiesfrontend/repl.py | 1 - bpython/curtsiesfrontend/replpainter.py | 1 - bpython/filelock.py | 1 - bpython/formatter.py | 1 - bpython/history.py | 1 - bpython/importcompletion.py | 1 - bpython/inspection.py | 1 - bpython/keys.py | 1 - bpython/lazyre.py | 1 - bpython/line.py | 1 - bpython/pager.py | 1 - bpython/paste.py | 1 - bpython/patch_linecache.py | 2 -- bpython/repl.py | 1 - bpython/simpleeval.py | 1 - bpython/simplerepl.py | 1 - bpython/test/test_curtsies.py | 1 - bpython/test/test_curtsies_painting.py | 1 - bpython/test/test_curtsies_parser.py | 2 -- bpython/test/test_curtsies_repl.py | 1 - bpython/test/test_importcompletion.py | 2 -- bpython/test/test_interpreter.py | 2 -- bpython/translations/__init__.py | 2 -- bpython/urwid.py | 1 - 39 files changed, 51 deletions(-) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index b1696e51b..b75e89a56 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -23,8 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import print_function, absolute_import - import os import sys import traceback diff --git a/bpdb/__main__.py b/bpdb/__main__.py index 0afaa17f8..a8fccf10f 100644 --- a/bpdb/__main__.py +++ b/bpdb/__main__.py @@ -22,8 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import - import sys if __name__ == "__main__": diff --git a/bpdb/debugger.py b/bpdb/debugger.py index 0da7b1024..e20c2265e 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -22,8 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import print_function - import pdb import bpython diff --git a/bpython/__init__.py b/bpython/__init__.py index 6d4a9aaa0..04e84e7b3 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -20,7 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import import os.path diff --git a/bpython/__main__.py b/bpython/__main__.py index 28c488702..5f92ba606 100644 --- a/bpython/__main__.py +++ b/bpython/__main__.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import import sys diff --git a/bpython/_internal.py b/bpython/_internal.py index e3907c7b1..a5d942e46 100644 --- a/bpython/_internal.py +++ b/bpython/_internal.py @@ -1,7 +1,5 @@ # encoding: utf-8 -from __future__ import absolute_import - import pydoc import sys diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index bebf4efd4..9e39e793b 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -34,7 +34,6 @@ - py3: True if the hosting Python runtime is of Python version 3 or later """ -from __future__ import absolute_import import sys import threading diff --git a/bpython/args.py b/bpython/args.py index 6aea8a197..c471c30ad 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -4,7 +4,6 @@ Module to handle command line argument parsing, for all front-ends. """ -from __future__ import print_function, absolute_import import code import imp diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a3c61ea9d..f2be44b09 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -23,7 +23,6 @@ # THE SOFTWARE. # -from __future__ import unicode_literals, absolute_import import __main__ import abc diff --git a/bpython/cli.py b/bpython/cli.py index 68d7f8566..bac099f1e 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -39,7 +39,6 @@ # - Instead the suspend key exits the program # - View source doesn't work on windows unless you install the less program (From GnuUtils or Cygwin) -from __future__ import division, absolute_import import platform import os diff --git a/bpython/clipboard.py b/bpython/clipboard.py index aee429b9e..6fa7f4e4e 100644 --- a/bpython/clipboard.py +++ b/bpython/clipboard.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import import subprocess import os diff --git a/bpython/config.py b/bpython/config.py index 1a58bf525..e2c5901b5 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -1,7 +1,5 @@ # encoding: utf-8 -from __future__ import unicode_literals, absolute_import - import os import sys import locale diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 24ceb6927..d812bd420 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -1,7 +1,5 @@ # encoding: utf-8 -from __future__ import absolute_import - import collections import io import logging diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 65b554ead..e4dc13ed9 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -1,7 +1,5 @@ # encoding: utf-8 -from __future__ import unicode_literals - import greenlet import time import curtsies.events as events diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 33f8c01ea..ec03dc1e4 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -1,5 +1,4 @@ # coding: utf-8 -from __future__ import unicode_literals from functools import partial import re diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 351a883b2..9da1f4745 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import contextlib import errno diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 73676fb69..d921aa703 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals import logging import itertools diff --git a/bpython/filelock.py b/bpython/filelock.py index 1e6e17087..c04201287 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import try: import fcntl diff --git a/bpython/formatter.py b/bpython/formatter.py index 54b1a264b..da2983777 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -26,7 +26,6 @@ # Pygments really kicks ass, it made it really easy to # get the exact behaviour I wanted, thanks Pygments.:) -from __future__ import absolute_import from pygments.formatter import Formatter from pygments.token import ( diff --git a/bpython/history.py b/bpython/history.py index f5c83aa42..bcfd0913a 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import unicode_literals, absolute_import import io import os import stat diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index bde59cf68..9de049750 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import from ._py3compat import py3, try_decode from .line import ( diff --git a/bpython/inspection.py b/bpython/inspection.py index 72237a245..033852d91 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import import inspect import io diff --git a/bpython/keys.py b/bpython/keys.py index 0f5b9bc28..5f8fab82d 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import import string from six.moves import range diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 7371ab042..1d15997c4 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import import re diff --git a/bpython/line.py b/bpython/line.py index 25ecc120e..83edb0a88 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -5,7 +5,6 @@ All functions take cursor offset from the beginning of the line and the line of Python code, and return None, or a tuple of the start index, end index, and the word.""" -from __future__ import unicode_literals, absolute_import from itertools import chain from collections import namedtuple diff --git a/bpython/pager.py b/bpython/pager.py index 2191a5f8d..65ad697b7 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import import curses import errno diff --git a/bpython/paste.py b/bpython/paste.py index ec29a6da5..180ea8ed4 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import from locale import getpreferredencoding from six.moves.urllib_parse import quote as urlquote, urljoin, urlparse diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index d2b1396b5..a3a620aeb 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -1,7 +1,5 @@ # encoding: utf-8 -from __future__ import absolute_import - import linecache diff --git a/bpython/repl.py b/bpython/repl.py index fd31edbbb..d6c655023 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from __future__ import absolute_import import code import inspect diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index c6a46ba35..24879fc81 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -28,7 +28,6 @@ """ -from __future__ import absolute_import import ast import inspect diff --git a/bpython/simplerepl.py b/bpython/simplerepl.py index 068f91b8a..6179fc938 100644 --- a/bpython/simplerepl.py +++ b/bpython/simplerepl.py @@ -25,7 +25,6 @@ the methods of bpython.curtsiesrepl.repl.BaseRepl that must be overridden. """ -from __future__ import unicode_literals, print_function, absolute_import import time import logging diff --git a/bpython/test/test_curtsies.py b/bpython/test/test_curtsies.py index b337350d1..23f2b8850 100644 --- a/bpython/test/test_curtsies.py +++ b/bpython/test/test_curtsies.py @@ -1,5 +1,4 @@ # coding: utf-8 -from __future__ import unicode_literals from collections import namedtuple diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 64a46b15c..28ac9b02a 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -1,6 +1,5 @@ # coding: utf8 -from __future__ import unicode_literals import itertools import os import pydoc diff --git a/bpython/test/test_curtsies_parser.py b/bpython/test/test_curtsies_parser.py index c0a20fcc0..bc1feef63 100644 --- a/bpython/test/test_curtsies_parser.py +++ b/bpython/test/test_curtsies_parser.py @@ -1,7 +1,5 @@ # encoding: utf-8 -from __future__ import unicode_literals - from bpython.test import unittest from bpython.curtsiesfrontend import parse from curtsies.fmtfuncs import yellow, cyan, green, bold diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 09db2d3b2..691b2f4c6 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -1,5 +1,4 @@ # coding: utf-8 -from __future__ import unicode_literals import code import os diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 0de09e799..a81d9d432 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -1,7 +1,5 @@ # encoding: utf-8 -from __future__ import unicode_literals - from bpython import importcompletion from bpython.test import unittest diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 4bdb67551..0bd070e0b 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - import sys import re from textwrap import dedent diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index 2c45ac5ce..8c2892f2d 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -1,7 +1,5 @@ # encoding: utf-8 -from __future__ import absolute_import - import gettext import locale import os.path diff --git a/bpython/urwid.py b/bpython/urwid.py index e0260873a..072f1c484 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -34,7 +34,6 @@ """ -from __future__ import absolute_import, division, print_function import sys import os From ebe19cc4a01ae88d81d16861a272d4946cabe352 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 14:30:40 +0200 Subject: [PATCH 1016/1650] Remove encoding header --- bpdb/__init__.py | 2 -- bpdb/__main__.py | 2 -- bpdb/debugger.py | 2 -- bpython/__main__.py | 2 -- bpython/_internal.py | 2 -- bpython/_py3compat.py | 2 -- bpython/args.py | 2 -- bpython/autocomplete.py | 2 -- bpython/clipboard.py | 2 -- bpython/config.py | 2 -- bpython/curtsies.py | 2 -- bpython/curtsiesfrontend/_internal.py | 2 -- bpython/curtsiesfrontend/coderunner.py | 2 -- bpython/curtsiesfrontend/events.py | 2 -- bpython/curtsiesfrontend/filewatch.py | 2 -- bpython/curtsiesfrontend/interaction.py | 2 -- bpython/curtsiesfrontend/interpreter.py | 2 -- bpython/curtsiesfrontend/manual_readline.py | 2 -- bpython/curtsiesfrontend/parse.py | 2 -- bpython/curtsiesfrontend/preprocess.py | 2 -- bpython/curtsiesfrontend/repl.py | 2 -- bpython/curtsiesfrontend/replpainter.py | 2 -- bpython/curtsiesfrontend/sitefix.py | 2 -- bpython/filelock.py | 2 -- bpython/formatter.py | 2 -- bpython/history.py | 2 -- bpython/importcompletion.py | 2 -- bpython/inspection.py | 2 -- bpython/keys.py | 2 -- bpython/lazyre.py | 2 -- bpython/line.py | 2 -- bpython/pager.py | 2 -- bpython/paste.py | 2 -- bpython/patch_linecache.py | 2 -- bpython/repl.py | 2 -- bpython/simpleeval.py | 2 -- bpython/simplerepl.py | 2 -- bpython/test/__init__.py | 2 -- bpython/test/test_args.py | 2 -- bpython/test/test_autocomplete.py | 2 -- bpython/test/test_config.py | 2 -- bpython/test/test_crashers.py | 2 -- bpython/test/test_curtsies.py | 2 -- bpython/test/test_curtsies_coderunner.py | 2 -- bpython/test/test_curtsies_painting.py | 2 -- bpython/test/test_curtsies_parser.py | 2 -- bpython/test/test_curtsies_repl.py | 2 -- bpython/test/test_filewatch.py | 2 -- bpython/test/test_history.py | 2 -- bpython/test/test_import_not_cyclical.py | 2 -- bpython/test/test_importcompletion.py | 2 -- bpython/test/test_inspection.py | 2 -- bpython/test/test_interpreter.py | 2 -- bpython/test/test_keys.py | 2 -- bpython/test/test_line_properties.py | 2 -- bpython/test/test_manual_readline.py | 2 -- bpython/test/test_preprocess.py | 2 -- bpython/test/test_repl.py | 2 -- bpython/test/test_simpleeval.py | 2 -- bpython/translations/__init__.py | 2 -- bpython/urwid.py | 2 -- setup.py | 2 -- 62 files changed, 124 deletions(-) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index b75e89a56..ea2a4cdee 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2008 Bob Farrell diff --git a/bpdb/__main__.py b/bpdb/__main__.py index a8fccf10f..5cbd26503 100644 --- a/bpdb/__main__.py +++ b/bpdb/__main__.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2013 Sebastian Ramacher diff --git a/bpdb/debugger.py b/bpdb/debugger.py index e20c2265e..d575b2671 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2008 Bob Farrell diff --git a/bpython/__main__.py b/bpython/__main__.py index 5f92ba606..9693dadf7 100644 --- a/bpython/__main__.py +++ b/bpython/__main__.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2015 Sebastian Ramacher diff --git a/bpython/_internal.py b/bpython/_internal.py index a5d942e46..e1609b8cb 100644 --- a/bpython/_internal.py +++ b/bpython/_internal.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import pydoc import sys diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index 9e39e793b..15a5dc482 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2012 the bpython authors. diff --git a/bpython/args.py b/bpython/args.py index c471c30ad..6f47571a0 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - """ Module to handle command line argument parsing, for all front-ends. """ diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index f2be44b09..a64ae0e41 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -1,5 +1,3 @@ -# coding: utf-8 - # The MIT License # # Copyright (c) 2009-2015 the bpython authors. diff --git a/bpython/clipboard.py b/bpython/clipboard.py index 6fa7f4e4e..555c15fe8 100644 --- a/bpython/clipboard.py +++ b/bpython/clipboard.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2015 Sebastian Ramacher diff --git a/bpython/config.py b/bpython/config.py index e2c5901b5..df88ec4e6 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import os import sys import locale diff --git a/bpython/curtsies.py b/bpython/curtsies.py index d812bd420..f550946a6 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import collections import io import logging diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 4f17cb41f..46ff1fa8c 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2015 the bpython authors. diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index d53e75d48..d198257a9 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - """For running Python code that could interrupt itself at any time in order to, for example, ask for a read on stdin, or a write on stdout diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index 15065d36e..9980f7be1 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - """Non-keyboard events used in bpython curtsies REPL""" import time diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 37ea8f509..61435271c 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import os from collections import defaultdict diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index e4dc13ed9..5bcd51fb0 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import greenlet import time import curtsies.events as events diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 57c694ce4..cbfd982c5 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import sys from six import iteritems, text_type diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 7448d4bf8..31bc439e4 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - """implementations of simple readline edit operations just the ones that fit the model of transforming the current line diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index ec03dc1e4..5d5e661af 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -1,5 +1,3 @@ -# coding: utf-8 - from functools import partial import re diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index 8169ee46a..714d9538a 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - """Tools for preparing code to be run in the REPL (removing blank lines, etc)""" diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9da1f4745..5dfd45281 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import contextlib import errno import greenlet diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index d921aa703..257fff24b 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import logging import itertools from six.moves import range diff --git a/bpython/curtsiesfrontend/sitefix.py b/bpython/curtsiesfrontend/sitefix.py index 23795a04b..e423d6c06 100644 --- a/bpython/curtsiesfrontend/sitefix.py +++ b/bpython/curtsiesfrontend/sitefix.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import sys from six.moves import builtins diff --git a/bpython/filelock.py b/bpython/filelock.py index c04201287..64d6f1353 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2015-2019 Sebastian Ramacher diff --git a/bpython/formatter.py b/bpython/formatter.py index da2983777..56196df33 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2008 Bob Farrell diff --git a/bpython/history.py b/bpython/history.py index bcfd0913a..a977a91a2 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2009 the bpython authors. diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 9de049750..e0379e7a9 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2009-2011 Andreas Stuehrk diff --git a/bpython/inspection.py b/bpython/inspection.py index 033852d91..2f507822f 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2009-2011 the bpython authors. diff --git a/bpython/keys.py b/bpython/keys.py index 5f8fab82d..f8e3ba012 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2008 Simon de Vlieger diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 1d15997c4..49af626de 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2015 Sebastian Ramacher diff --git a/bpython/line.py b/bpython/line.py index 83edb0a88..6f1f75e16 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - """Extracting and changing portions of the current line All functions take cursor offset from the beginning of the line and the line of diff --git a/bpython/pager.py b/bpython/pager.py index 65ad697b7..97ea8f56c 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2009-2011 Andreas Stuehrk diff --git a/bpython/paste.py b/bpython/paste.py index 180ea8ed4..72762fb56 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2014-2015 Sebastian Ramacher diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index a3a620aeb..dba099c32 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import linecache diff --git a/bpython/repl.py b/bpython/repl.py index d6c655023..d8ff473c9 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2009-2011 the bpython authors. diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 24879fc81..21a040243 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2015 the bpython authors. diff --git a/bpython/simplerepl.py b/bpython/simplerepl.py index 6179fc938..85595d331 100644 --- a/bpython/simplerepl.py +++ b/bpython/simplerepl.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # The MIT License # # Copyright (c) 2015 the bpython authors. diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index 7ff693c0a..acc813745 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - try: import unittest2 as unittest except ImportError: diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 820f790df..3e8c5e9db 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import re import subprocess import sys diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 4cbbf628a..f6a366906 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - from collections import namedtuple import inspect import keyword diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index e8307ea1a..a4d081da9 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import os import tempfile import textwrap diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 9b6382640..ef61322dc 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import fcntl import os import pty diff --git a/bpython/test/test_curtsies.py b/bpython/test/test_curtsies.py index 23f2b8850..cc8619e2f 100644 --- a/bpython/test/test_curtsies.py +++ b/bpython/test/test_curtsies.py @@ -1,5 +1,3 @@ -# coding: utf-8 - from collections import namedtuple from bpython.curtsies import combined_events diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index 94d8106d6..1cdb33669 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import sys from bpython.test import mock, unittest diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 28ac9b02a..b5947303f 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -1,5 +1,3 @@ -# coding: utf8 - import itertools import os import pydoc diff --git a/bpython/test/test_curtsies_parser.py b/bpython/test/test_curtsies_parser.py index bc1feef63..ede87460d 100644 --- a/bpython/test/test_curtsies_parser.py +++ b/bpython/test/test_curtsies_parser.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - from bpython.test import unittest from bpython.curtsiesfrontend import parse from curtsies.fmtfuncs import yellow, cyan, green, bold diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 691b2f4c6..c01a26239 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -1,5 +1,3 @@ -# coding: utf-8 - import code import os import sys diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index 2a3b6f0de..9f2790b28 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import os try: diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index f095104e3..2227cfe04 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import io import os diff --git a/bpython/test/test_import_not_cyclical.py b/bpython/test/test_import_not_cyclical.py index 6b9581853..c2ebbe0ee 100644 --- a/bpython/test/test_import_not_cyclical.py +++ b/bpython/test/test_import_not_cyclical.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import os import sys import tempfile diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index a81d9d432..4b018f904 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - from bpython import importcompletion from bpython.test import unittest diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index ecb2a6e9f..e3c2be9f8 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import os from bpython._py3compat import py3 diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 0bd070e0b..36aa06a37 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import sys import re from textwrap import dedent diff --git a/bpython/test/test_keys.py b/bpython/test/test_keys.py index 75f62353c..671182bc3 100644 --- a/bpython/test/test_keys.py +++ b/bpython/test/test_keys.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - from bpython import keys from bpython.test import unittest diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 35169c6ac..f139b3103 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import re from bpython.test import unittest diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index aedfddfe2..2218831c1 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - from bpython.curtsiesfrontend.manual_readline import ( left_arrow, right_arrow, diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index 7fb782989..364c8ecd9 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - from code import compile_command as compiler from functools import partial import difflib diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index d76dadc60..da2cd746b 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - from itertools import islice from six.moves import range import collections diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 2e12fcb4d..ee4439e02 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import ast import numbers import sys diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index 8c2892f2d..c3e1b0bf7 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - import gettext import locale import os.path diff --git a/bpython/urwid.py b/bpython/urwid.py index 072f1c484..9667c05da 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - # # The MIT License # diff --git a/setup.py b/setup.py index 166294865..e180b7aba 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- - import os import platform From d56e832fe376409904930c3bb5393ab0bdead680 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 14:40:10 +0200 Subject: [PATCH 1017/1650] Remove Python 2 specific tests --- bpython/test/__init__.py | 17 +-- bpython/test/test_autocomplete.py | 58 +------ bpython/test/test_curtsies_repl.py | 14 +- bpython/test/test_import_not_cyclical.py | 2 - bpython/test/test_inspection.py | 25 --- bpython/test/test_interpreter.py | 187 +---------------------- bpython/test/test_repl.py | 10 +- bpython/test/test_simpleeval.py | 5 - setup.py | 1 - 9 files changed, 10 insertions(+), 309 deletions(-) diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index acc813745..90b97729c 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -1,15 +1,7 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest - -try: - from unittest import mock -except ImportError: - import mock +import unittest +from unittest import mock from bpython.translations import init -from bpython._py3compat import py3 from six.moves import builtins import os @@ -22,10 +14,7 @@ def setUpClass(cls): class MagicIterMock(mock.MagicMock): - if py3: - __next__ = mock.Mock(return_value=None) - else: - next = mock.Mock(return_value=None) + __next__ = mock.Mock(return_value=None) def builtin_target(obj): diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index f6a366906..40330c77a 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -16,13 +16,9 @@ has_jedi = False from bpython import autocomplete -from bpython._py3compat import py3 from bpython.test import mock -if py3: - glob_function = "glob.iglob" -else: - glob_function = "glob.glob" +glob_function = "glob.iglob" class TestSafeEval(unittest.TestCase): @@ -235,11 +231,6 @@ def method(self, x): pass -skip_old_style = unittest.skipIf( - py3, "In Python 3 there are no old style classes" -) - - class Properties(Foo): @property def asserts_when_called(self): @@ -266,35 +257,6 @@ def test_att_matches_found_on_instance(self): set(["a.method", "a.a", "a.b"]), ) - @skip_old_style - def test_att_matches_found_on_old_style_instance(self): - self.assertSetEqual( - self.com.matches(2, "a.", locals_={"a": OldStyleFoo()}), - set(["a.method", "a.a", "a.b"]), - ) - self.assertIn( - u"a.__dict__", - self.com.matches(4, "a.__", locals_={"a": OldStyleFoo()}), - ) - - @skip_old_style - def test_att_matches_found_on_old_style_class_object(self): - self.assertIn( - u"A.__dict__", - self.com.matches(4, "A.__", locals_={"A": OldStyleFoo}), - ) - - @skip_old_style - def test_issue536(self): - class OldStyleWithBrokenGetAttr: - def __getattr__(self, attr): - raise Exception() - - locals_ = {"a": OldStyleWithBrokenGetAttr()} - self.assertIn( - u"a.__module__", self.com.matches(4, "a.__", locals_=locals_) - ) - def test_descriptor_attributes_not_run(self): com = autocomplete.AttrCompletion() self.assertSetEqual( @@ -327,13 +289,6 @@ def test_att_matches_found_on_instance(self): set(["method", "a", "b"]), ) - @skip_old_style - def test_att_matches_found_on_old_style_instance(self): - self.assertSetEqual( - self.com.matches(5, "a[0].", locals_={"a": [OldStyleFoo()]}), - set(["method", "a", "b"]), - ) - def test_other_getitem_methods_not_called(self): class FakeList(object): def __getitem__(inner_self, i): @@ -438,7 +393,6 @@ def test_completions_starting_with_different_cases(self): ) self.assertSetEqual(matches, set(["ade"])) - @unittest.skipUnless(py3, "asyncio required") def test_issue_544(self): com = autocomplete.MultilineJediCompletion() code = "@asyncio.coroutine\ndef" @@ -463,10 +417,6 @@ def test_completions_are_unicode(self): for m in self.com.matches(1, "a", locals_={"abc": 10}): self.assertIsInstance(m, type(u"")) - @unittest.skipIf(py3, "in Python 3 invalid identifiers are passed through") - def test_ignores_nonascii_encodable(self): - self.assertEqual(self.com.matches(3, "abc", locals_={"abcß": 10}), None) - def test_mock_kwlist(self): with mock.patch.object(keyword, "kwlist", new=["abcd"]): self.assertEqual(self.com.matches(3, "abc", locals_={}), None) @@ -481,11 +431,7 @@ def test_set_of_params_returns_when_matches_found(self): def func(apple, apricot, banana, carrot): pass - if py3: - argspec = list(inspect.getfullargspec(func)) - else: - argspec = list(inspect.getargspec(func)) - + argspec = list(inspect.getfullargspec(func)) argspec = ["func", argspec, False] com = autocomplete.ParameterNameCompletion() self.assertSetEqual( diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index c01a26239..885856c9b 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -14,7 +14,6 @@ from bpython import autocomplete from bpython import config from bpython import args -from bpython._py3compat import py3 from bpython.test import ( FixLanguageTestCase as TestCase, MagicIterMock, @@ -24,13 +23,7 @@ ) from curtsies import events - -if py3: - from importlib import invalidate_caches -else: - - def invalidate_caches(): - """Does not exist before Python 3.3""" +from importlib import invalidate_caches def setup_config(conf): @@ -110,10 +103,7 @@ def test_get_last_word_with_prev_line(self): def mock_next(obj, return_value): - if py3: - obj.__next__.return_value = return_value - else: - obj.next.return_value = return_value + obj.__next__.return_value = return_value class TestCurtsiesReplTab(TestCase): diff --git a/bpython/test/test_import_not_cyclical.py b/bpython/test/test_import_not_cyclical.py index c2ebbe0ee..830a23117 100644 --- a/bpython/test/test_import_not_cyclical.py +++ b/bpython/test/test_import_not_cyclical.py @@ -2,12 +2,10 @@ import sys import tempfile -from bpython._py3compat import py3 from bpython.test import unittest from bpython.importcompletion import find_modules -@unittest.skipIf(not py3, "Test doesn't work in python 2.") class TestAvoidSymbolicLinks(unittest.TestCase): def setUp(self): with tempfile.TemporaryDirectory() as import_test_folder: diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index e3c2be9f8..991824f48 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -1,6 +1,5 @@ import os -from bpython._py3compat import py3 from bpython import inspection from bpython.test import unittest from bpython.test.fodder import encoding_ascii @@ -58,16 +57,6 @@ def test_is_callable(self): self.assertFalse(inspection.is_callable(None)) self.assertTrue(inspection.is_callable(CallableMethod().method)) - @unittest.skipIf(py3, "old-style classes only exist in Python 2") - def test_is_new_style_py2(self): - self.assertTrue(inspection.is_new_style(spam)) - self.assertTrue(inspection.is_new_style(Noncallable)) - self.assertFalse(inspection.is_new_style(OldNoncallable)) - self.assertTrue(inspection.is_new_style(Noncallable())) - self.assertFalse(inspection.is_new_style(OldNoncallable())) - self.assertTrue(inspection.is_new_style(None)) - - @unittest.skipUnless(py3, "only in Python 3 are all classes new-style") def test_is_new_style_py3(self): self.assertTrue(inspection.is_new_style(spam)) self.assertTrue(inspection.is_new_style(Noncallable)) @@ -170,12 +159,6 @@ def prop(self): class Slots(object): __slots__ = ["s1", "s2", "s3"] - if not py3: - - @property - def s3(self): - raise AssertionError("Property __get__ executed") - class SlotsSubclass(Slots): @property @@ -248,14 +231,6 @@ def test_lookup_on_slots_classes(self): self.assertEqual(inspection.hasattr_safe(s, "s1"), False) self.assertEqual(inspection.hasattr_safe(s, "s4"), True) - @unittest.skipIf(py3, "Py 3 doesn't allow slots and prop in same class") - def test_lookup_with_property_and_slots(self): - sga = inspection.getattr_safe - s = SlotsSubclass() - self.assertIsInstance(sga(Slots, "s3"), property) - self.assertEqual(inspection.getattr_safe(s, "s3"), Slots.__dict__["s3"]) - self.assertIsInstance(sga(SlotsSubclass, "s3"), property) - def test_lookup_on_overridden_methods(self): sga = inspection.getattr_safe self.assertEqual(sga(OverriddenGetattr(), "a"), 1) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 36aa06a37..56d0c8ad7 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -5,7 +5,6 @@ from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain from bpython.curtsiesfrontend import interpreter -from bpython._py3compat import py3 from bpython.test import mock, unittest pypy = "PyPy" in sys.version @@ -87,10 +86,7 @@ def gfunc(): i.runsource("gfunc()") - if pypy and not py3: - global_not_found = "global name 'gfunc' is not defined" - else: - global_not_found = "name 'gfunc' is not defined" + global_not_found = "name 'gfunc' is not defined" expected = ( "Traceback (most recent call last):\n File " @@ -109,19 +105,6 @@ def gfunc(): self.assertMultiLineEqual(str(plain("").join(a)), str(expected)) self.assertEqual(plain("").join(a), expected) - @unittest.skipIf(py3, "runsource() accepts only unicode in Python 3") - def test_runsource_bytes(self): - i = interpreter.Interp(encoding=b"latin-1") - - i.runsource("a = b'\xfe'".encode("latin-1"), encode=False) - self.assertIsInstance(i.locals["a"], str) - self.assertEqual(i.locals["a"], b"\xfe") - - i.runsource("b = u'\xfe'".encode("latin-1"), encode=False) - self.assertIsInstance(i.locals["b"], unicode) - self.assertEqual(i.locals["b"], "\xfe") - - @unittest.skipUnless(py3, "Only a syntax error in Python 3") def test_runsource_bytes_over_128_syntax_error_py3(self): i = interpreter.Interp(encoding=b"latin-1") i.showsyntaxerror = mock.Mock(return_value=None) @@ -129,22 +112,6 @@ def test_runsource_bytes_over_128_syntax_error_py3(self): i.runsource("a = b'\xfe'") i.showsyntaxerror.assert_called_with(mock.ANY) - @unittest.skipIf(py3, "encode is Python 2 only") - def test_runsource_bytes_over_128_syntax_error_py2(self): - i = interpreter.Interp(encoding=b"latin-1") - - i.runsource(b"a = b'\xfe'") - self.assertIsInstance(i.locals["a"], type(b"")) - self.assertEqual(i.locals["a"], b"\xfe") - - @unittest.skipIf(py3, "encode is Python 2 only") - def test_runsource_unicode(self): - i = interpreter.Interp(encoding=b"latin-1") - - i.runsource("a = u'\xfe'") - self.assertIsInstance(i.locals["a"], type("")) - self.assertEqual(i.locals["a"], "\xfe") - def test_getsource_works_on_interactively_defined_functions(self): source = "def foo(x):\n return x + 1\n" i = interpreter.Interp() @@ -153,155 +120,3 @@ def test_getsource_works_on_interactively_defined_functions(self): inspected_source = inspect.getsource(i.locals["foo"]) self.assertEqual(inspected_source, source) - - @unittest.skipIf(py3, "encode only does anything in Python 2") - def test_runsource_unicode_autoencode_and_noencode(self): - """error line numbers should be fixed""" - - # Since correct behavior for unicode is the same - # for auto and False, run the same tests - for encode in ["auto", False]: - i, a = self.interp_errlog() - i.runsource("[1 + 1,\nabcd]", encode=encode) - self.assertEqual(self.err_lineno(a), 2) - - i, a = self.interp_errlog() - i.runsource("[1 + 1,\nabcd]", encode=encode) - self.assertEqual(self.err_lineno(a), 2) - - i, a = self.interp_errlog() - i.runsource("#encoding: utf-8\nabcd", encode=encode) - self.assertEqual(self.err_lineno(a), 2) - - i, a = self.interp_errlog() - i.runsource( - "#encoding: utf-8\nabcd", filename="x.py", encode=encode - ) - self.assertIn( - "SyntaxError:", - "".join("".join(remove_ansi(x.__unicode__()) for x in a)), - ) - - @unittest.skipIf(py3, "encode only does anything in Python 2") - def test_runsource_unicode_encode(self): - i, _ = self.interp_errlog() - with self.assertRaises(ValueError): - i.runsource("1 + 1", encode=True) - - i, _ = self.interp_errlog() - with self.assertRaises(ValueError): - i.runsource("1 + 1", filename="x.py", encode=True) - - @unittest.skipIf(py3, "encode only does anything in Python 2") - def test_runsource_bytestring_noencode(self): - i, a = self.interp_errlog() - i.runsource(b"[1 + 1,\nabcd]", encode=False) - self.assertEqual(self.err_lineno(a), 2) - - i, a = self.interp_errlog() - i.runsource(b"[1 + 1,\nabcd]", filename="x.py", encode=False) - self.assertEqual(self.err_lineno(a), 2) - - i, a = self.interp_errlog() - i.runsource( - dedent( - b"""\ - #encoding: utf-8 - - ["%s", - abcd]""" - % ("åß∂ƒ".encode("utf8"),) - ), - encode=False, - ) - self.assertEqual(self.err_lineno(a), 4) - - i, a = self.interp_errlog() - i.runsource( - dedent( - b"""\ - #encoding: utf-8 - - ["%s", - abcd]""" - % ("åß∂ƒ".encode("utf8"),) - ), - filename="x.py", - encode=False, - ) - self.assertEqual(self.err_lineno(a), 4) - - @unittest.skipIf(py3, "encode only does anything in Python 2") - def test_runsource_bytestring_encode(self): - i, a = self.interp_errlog() - i.runsource(b"[1 + 1,\nabcd]", encode=True) - self.assertEqual(self.err_lineno(a), 2) - - i, a = self.interp_errlog() - with self.assertRaises(ValueError): - i.runsource(b"[1 + 1,\nabcd]", filename="x.py", encode=True) - - i, a = self.interp_errlog() - i.runsource( - dedent( - b"""\ - #encoding: utf-8 - - [u"%s", - abcd]""" - % ("åß∂ƒ".encode("utf8"),) - ), - encode=True, - ) - self.assertEqual(self.err_lineno(a), 4) - - i, a = self.interp_errlog() - with self.assertRaises(ValueError): - i.runsource( - dedent( - b"""\ - #encoding: utf-8 - - [u"%s", - abcd]""" - % ("åß∂ƒ".encode("utf8"),) - ), - filename="x.py", - encode=True, - ) - - @unittest.skipIf(py3, "encode only does anything in Python 2") - def test_runsource_bytestring_autoencode(self): - i, a = self.interp_errlog() - i.runsource(b"[1 + 1,\n abcd]") - self.assertEqual(self.err_lineno(a), 2) - - i, a = self.interp_errlog() - i.runsource(b"[1 + 1,\nabcd]", filename="x.py") - self.assertEqual(self.err_lineno(a), 2) - - i, a = self.interp_errlog() - i.runsource( - dedent( - b"""\ - #encoding: utf-8 - - [u"%s", - abcd]""" - % ("åß∂ƒ".encode("utf8"),) - ) - ) - self.assertEqual(self.err_lineno(a), 4) - - i, a = self.interp_errlog() - i.runsource( - dedent( - b"""\ - #encoding: utf-8 - - [u"%s", - abcd]""" - % ("åß∂ƒ".encode("utf8"),) - ) - ) - self.assertEqual(self.err_lineno(a), 4) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index da2cd746b..8246c9931 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -8,7 +8,6 @@ import sys import tempfile -from bpython._py3compat import py3 from bpython import config, repl, cli, autocomplete from bpython.test import MagicIterMock, mock, FixLanguageTestCase as TestCase from bpython.test import unittest, TEST_CONFIG @@ -394,9 +393,7 @@ def test_fuzzy_global_complete(self): self.assertTrue(hasattr(self.repl.matches_iter, "matches")) self.assertEqual( self.repl.matches_iter.matches, - ["UnboundLocalError(", "__doc__"] - if not py3 - else ["ChildProcessError(", "UnboundLocalError(", "__doc__"], + ["ChildProcessError(", "UnboundLocalError(", "__doc__"], ) # 2. Attribute tests @@ -501,10 +498,7 @@ def setUp(self): # 3 Types of tab complete def test_simple_tab_complete(self): self.repl.matches_iter = MagicIterMock() - if py3: - self.repl.matches_iter.__bool__.return_value = False - else: - self.repl.matches_iter.__nonzero__.return_value = False + self.repl.matches_iter.__bool__.return_value = False self.repl.complete = mock.Mock() self.repl.print_line = mock.Mock() self.repl.matches_iter.is_cseq.return_value = False diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index ee4439e02..f398e00ea 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -8,7 +8,6 @@ EvaluationError, ) from bpython.test import unittest -from bpython._py3compat import py3 class TestSimpleEval(unittest.TestCase): @@ -19,10 +18,6 @@ def test_matches_stdlib(self): """Should match the stdlib literal_eval if no names or indexing""" self.assertMatchesStdlib("[1]") self.assertMatchesStdlib("{(1,): [2,3,{}]}") - - @unittest.skipUnless(py3, "Only Python3 versions of ast.literal_eval evaluate set literals") - def test_matches_stdlib_py3(self): - """Should match the stdlib literal_eval if no names or indexing""" self.assertMatchesStdlib("{1, 2}") @unittest.skipUnless(sys.version_info[:2] >= (3, 9), "Only Python3.9 evaluates set()") diff --git a/setup.py b/setup.py index e180b7aba..d87ba8135 100755 --- a/setup.py +++ b/setup.py @@ -215,7 +215,6 @@ def initialize_options(self): data_files.extend(man_pages) classifiers = [ - "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", ] From 8f77951ac9c126edbd69d634f1d22244fd7fe6df Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 14:52:47 +0200 Subject: [PATCH 1018/1650] Remove use of six.moves.range --- bpython/autocomplete.py | 2 +- bpython/cli.py | 1 - bpython/curtsiesfrontend/repl.py | 1 - bpython/curtsiesfrontend/replpainter.py | 1 - bpython/history.py | 1 - bpython/importcompletion.py | 1 - bpython/inspection.py | 1 - bpython/keys.py | 1 - bpython/test/test_history.py | 1 - bpython/test/test_repl.py | 1 - 10 files changed, 1 insertion(+), 10 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a64ae0e41..ec61d6471 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -30,7 +30,7 @@ import os import re import rlcompleter -from six.moves import range, builtins +from six.moves import builtins from six import string_types, iteritems from . import inspection diff --git a/bpython/cli.py b/bpython/cli.py index bac099f1e..ce51d718b 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -58,7 +58,6 @@ import unicodedata import errno -from six.moves import range # These are used for syntax highlighting from pygments import format diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 5dfd45281..ed6fe9c2b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -10,7 +10,6 @@ import tempfile import time import unicodedata -from six.moves import range from pygments import format as pygformat from bpython._py3compat import PythonLexer diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 257fff24b..3211aab36 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -1,6 +1,5 @@ import logging import itertools -from six.moves import range from curtsies import fsarray, fmtstr, FSArray from curtsies.formatstring import linesplit diff --git a/bpython/history.py b/bpython/history.py index a977a91a2..8ecae469e 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -25,7 +25,6 @@ import os import stat from itertools import islice -from six.moves import range from .translations import _ from .filelock import FileLock diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index e0379e7a9..c54909a39 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -33,7 +33,6 @@ import os import sys import warnings -from six.moves import filter if py3: import importlib.machinery diff --git a/bpython/inspection.py b/bpython/inspection.py index 2f507822f..7f58ba193 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -27,7 +27,6 @@ import keyword import pydoc from collections import namedtuple -from six.moves import range from pygments.token import Token from types import MemberDescriptorType diff --git a/bpython/keys.py b/bpython/keys.py index f8e3ba012..46e72493f 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -22,7 +22,6 @@ import string -from six.moves import range class KeyMap(object): diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index 2227cfe04..f8e931ada 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -1,7 +1,6 @@ import io import os -from six.moves import range from bpython.config import getpreferredencoding from bpython.history import History diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 8246c9931..e636b201f 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,5 +1,4 @@ from itertools import islice -from six.moves import range import collections import inspect import os From 07ec4e95d7c40356ef1f7303e2ddf8394dd9896a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 17:47:35 +0200 Subject: [PATCH 1019/1650] Remove PythonLexer from bpython._py3compat --- bpython/_py3compat.py | 8 -------- bpython/cli.py | 5 +++-- bpython/curtsiesfrontend/repl.py | 4 ++-- bpython/inspection.py | 5 +++-- bpython/repl.py | 9 +++++---- 5 files changed, 13 insertions(+), 18 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index 15a5dc482..380cf00fc 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -27,8 +27,6 @@ Defines the following attributes: - - PythonLexer: Pygment's Python lexer matching the hosting runtime's - Python version. - py3: True if the hosting Python runtime is of Python version 3 or later """ @@ -39,12 +37,6 @@ py3 = sys.version_info[0] == 3 -if py3: - from pygments.lexers import Python3Lexer as PythonLexer -else: - from pygments.lexers import PythonLexer - - if py3 or sys.version_info[:3] >= (2, 7, 3): def prepare_for_exec(arg, encoding=None): diff --git a/bpython/cli.py b/bpython/cli.py index ce51d718b..f83c7243f 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -62,7 +62,8 @@ # These are used for syntax highlighting from pygments import format from pygments.formatters import TerminalFormatter -from ._py3compat import PythonLexer, py3 +from pygments.lexers import Python3Lexer +from ._py3compat import py3 from pygments.token import Token from .formatter import BPythonFormatter @@ -1001,7 +1002,7 @@ def p_key(self, key): else: if config.highlight_show_source: source = format( - PythonLexer().get_tokens(source), TerminalFormatter() + Python3Lexer().get_tokens(source), TerminalFormatter() ) page(source) return "" diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index ed6fe9c2b..99710865e 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -12,7 +12,7 @@ import unicodedata from pygments import format as pygformat -from bpython._py3compat import PythonLexer +from pygments.lexers import Python3Lexer from pygments.formatters import TerminalFormatter from wcwidth import wcswidth @@ -2013,7 +2013,7 @@ def show_source(self): else: if self.config.highlight_show_source: source = pygformat( - PythonLexer().get_tokens(source), TerminalFormatter() + Python3Lexer().get_tokens(source), TerminalFormatter() ) self.pager(source) diff --git a/bpython/inspection.py b/bpython/inspection.py index 7f58ba193..088acdfe6 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -29,9 +29,10 @@ from collections import namedtuple from pygments.token import Token +from pygments.lexers import Python3Lexer from types import MemberDescriptorType -from ._py3compat import PythonLexer, py3 +from ._py3compat import py3 from .lazyre import LazyReCompile if not py3: @@ -134,7 +135,7 @@ def __repr__(self): def parsekeywordpairs(signature): - tokens = PythonLexer().get_tokens(signature) + tokens = Python3Lexer().get_tokens(signature) preamble = True stack = [] substack = [] diff --git a/bpython/repl.py b/bpython/repl.py index d8ff473c9..4d23cdf68 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -40,10 +40,11 @@ from types import ModuleType from pygments.token import Token +from pygments.lexers import Python3Lexer from . import autocomplete from . import inspection -from ._py3compat import PythonLexer, py3, prepare_for_exec +from ._py3compat import py3, prepare_for_exec from .clipboard import get_clipboard, CopyFailed from .config import getpreferredencoding from .formatter import Parenthesis @@ -572,7 +573,7 @@ def _funcname_and_argnum(cls, line): # argument so we're done counting stack = [["", "", 0, ""]] try: - for (token, value) in PythonLexer().get_tokens(line): + for (token, value) in Python3Lexer().get_tokens(line): if token is Token.Punctuation: if value in "([{": stack.append(["", "", 0, value]) @@ -1079,7 +1080,7 @@ def tokenize(self, s, newline=False): if self.cpos: cursor += 1 stack = list() - all_tokens = list(PythonLexer().get_tokens(source)) + all_tokens = list(Python3Lexer().get_tokens(source)) # Unfortunately, Pygments adds a trailing newline and strings with # no size, so strip them while not all_tokens[-1][1]: @@ -1250,7 +1251,7 @@ def next_indentation(line, tab_length): def next_token_inside_string(code_string, inside_string): """Given a code string s and an initial state inside_string, return whether the next token will be inside a string or not.""" - for token, value in PythonLexer().get_tokens(code_string): + for token, value in Python3Lexer().get_tokens(code_string): if token is Token.String: value = value.lstrip("bBrRuU") if value in ['"""', "'''", '"', "'"]: From e858ed1ca7c4b6af3128b19004fa2c4d9c58e020 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 17:59:28 +0200 Subject: [PATCH 1020/1650] Remove bpython._py3compat.prepare_for_exec --- bpython/_py3compat.py | 12 ------------ bpython/repl.py | 15 +++++---------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index 380cf00fc..d1be99437 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -37,18 +37,6 @@ py3 = sys.version_info[0] == 3 -if py3 or sys.version_info[:3] >= (2, 7, 3): - - def prepare_for_exec(arg, encoding=None): - return arg - - -else: - - def prepare_for_exec(arg, encoding=None): - return arg.encode(encoding) - - if py3: def try_decode(s, encoding): diff --git a/bpython/repl.py b/bpython/repl.py index 4d23cdf68..02774b340 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -44,7 +44,7 @@ from . import autocomplete from . import inspection -from ._py3compat import py3, prepare_for_exec +from ._py3compat import py3 from .clipboard import get_clipboard, CopyFailed from .config import getpreferredencoding from .formatter import Parenthesis @@ -1169,14 +1169,12 @@ def send_to_external_editor(self, text): exited with non-zero""" encoding = getpreferredencoding() - editor_args = shlex.split( - prepare_for_exec(self.config.editor, encoding) - ) + editor_args = shlex.split(self.config.editor) with tempfile.NamedTemporaryFile(suffix=".py") as temp: temp.write(text.encode(encoding)) temp.flush() - args = editor_args + [prepare_for_exec(temp.name, encoding)] + args = editor_args + [temp.name] if subprocess.call(args) == 0: with open(temp.name) as f: if py3: @@ -1187,11 +1185,8 @@ def send_to_external_editor(self, text): return text def open_in_external_editor(self, filename): - encoding = getpreferredencoding() - editor_args = shlex.split( - prepare_for_exec(self.config.editor, encoding) - ) - args = editor_args + [prepare_for_exec(filename, encoding)] + editor_args = shlex.split(self.config.editor) + args = editor_args + [filename] return subprocess.call(args) == 0 def edit_config(self): From d4ce21e0ab66e88ff0df2b48fb0e225ca016745f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 18:02:19 +0200 Subject: [PATCH 1021/1650] Move bpython._py3compat.is_main_thread to bpython.curtsiesfrontend.code --- bpython/_py3compat.py | 13 ------------- bpython/curtsiesfrontend/coderunner.py | 7 ++++++- bpython/curtsiesfrontend/repl.py | 4 ++-- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index d1be99437..1d9091cc6 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -32,7 +32,6 @@ import sys -import threading py3 = sys.version_info[0] == 3 @@ -53,15 +52,3 @@ def try_decode(s, encoding): except UnicodeDecodeError: return None return s - - -if py3: - - def is_main_thread(): - return threading.main_thread() == threading.current_thread() - - -else: - - def is_main_thread(): - return isinstance(threading.current_thread(), threading._MainThread) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index d198257a9..0eceeb33e 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -14,13 +14,18 @@ import signal import greenlet import logging +import threading -from bpython._py3compat import py3, is_main_thread +from bpython._py3compat import py3 from bpython.config import getpreferredencoding logger = logging.getLogger(__name__) +def is_main_thread(): + return threading.main_thread() == threading.current_thread() + + class SigintHappened(object): """If this class is returned, a SIGINT happened while the main greenlet""" diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 99710865e..9e8da8cff 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -36,12 +36,12 @@ from bpython.formatter import BPythonFormatter from bpython import autocomplete from bpython.translations import _ -from bpython._py3compat import py3, is_main_thread +from bpython._py3compat import py3 from bpython.pager import get_pager_command from bpython.curtsiesfrontend import replpainter as paint from bpython.curtsiesfrontend import sitefix -from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput +from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput, is_main_thread from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler from bpython.curtsiesfrontend.interaction import StatusBar from bpython.curtsiesfrontend.manual_readline import edit_keys From 1ef1776711ad08845311d3df16430ddcf90d59c7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 18:06:59 +0200 Subject: [PATCH 1022/1650] Remove bpython._py3compat.try_decode --- bpython/_py3compat.py | 18 ------------------ bpython/autocomplete.py | 5 ++--- bpython/importcompletion.py | 20 ++++++++------------ 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py index 1d9091cc6..1a4c6d226 100644 --- a/bpython/_py3compat.py +++ b/bpython/_py3compat.py @@ -34,21 +34,3 @@ import sys py3 = sys.version_info[0] == 3 - - -if py3: - - def try_decode(s, encoding): - return s - - -else: - - def try_decode(s, encoding): - """Try to decode s which is str names. Return None if not decodable""" - if not isinstance(s, unicode): - try: - return s.decode(encoding) - except UnicodeDecodeError: - return None - return s diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index ec61d6471..2f46e7d3c 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -37,7 +37,7 @@ from . import importcompletion from . import line as lineparts from .line import LinePart -from ._py3compat import py3, try_decode +from ._py3compat import py3 from .lazyre import LazyReCompile from .simpleeval import safe_eval, evaluate_current_expression, EvaluationError @@ -470,7 +470,6 @@ def matches(self, cursor_offset, line, **kwargs): matches.add(word) for nspace in (builtins.__dict__, locals_): for word, val in iteritems(nspace): - word = try_decode(word, "ascii") # if identifier isn't ascii, don't complete (syntax error) if word is None: continue @@ -580,7 +579,7 @@ def matches(self, cursor_offset, line, **kwargs): first_letter = line[self._orig_start : self._orig_start + 1] - matches = [try_decode(c.name, "ascii") for c in completions] + matches = [c.name for c in completions] if any( not m.lower().startswith(matches[0][0].lower()) for m in matches ): diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index c54909a39..7114f01ae 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -21,7 +21,7 @@ # THE SOFTWARE. -from ._py3compat import py3, try_decode +from ._py3compat import py3 from .line import ( current_word, current_import, @@ -76,22 +76,21 @@ def attr_matches(cw, prefix="", only_modules=False): return set() module = sys.modules[module_name] if only_modules: - matches = ( + matches = { name for name in dir(module) if name.startswith(name_after_dot) and "%s.%s" % (module_name, name) in sys.modules - ) + } else: - matches = ( + matches = { name for name in dir(module) if name.startswith(name_after_dot) - ) + } module_part, _, _ = cw.rpartition(".") if module_part: - matches = ("%s.%s" % (module_part, m) for m in matches) + matches = {"%s.%s" % (module_part, m) for m in matches} - generator = (try_decode(match, "ascii") for match in matches) - return set(filter(lambda x: x is not None, generator)) + return matches def module_attr_matches(name): @@ -210,16 +209,13 @@ def find_all_modules(path=None): """Return a list with all modules in `path`, which should be a list of directory names. If path is not given, sys.path will be used.""" if path is None: - modules.update(try_decode(m, "ascii") for m in sys.builtin_module_names) + modules.update(sys.builtin_module_names) path = sys.path for p in path: if not p: p = os.curdir for module in find_modules(p): - module = try_decode(module, "ascii") - if module is None: - continue modules.add(module) yield From 02b4d6b88f4a755c8e0e220a23ac703a4d99a16d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 18:36:39 +0200 Subject: [PATCH 1023/1650] Remove bpython._py3compat.py3 --- bpython/_py3compat.py | 36 -------- bpython/autocomplete.py | 66 +++------------ bpython/cli.py | 88 ++++---------------- bpython/curtsies.py | 9 -- bpython/curtsiesfrontend/_internal.py | 3 - bpython/curtsiesfrontend/coderunner.py | 3 - bpython/curtsiesfrontend/manual_readline.py | 6 +- bpython/curtsiesfrontend/repl.py | 39 +-------- bpython/curtsiesfrontend/replpainter.py | 22 ++--- bpython/importcompletion.py | 38 +++------ bpython/inspection.py | 92 ++++----------------- bpython/pager.py | 6 +- bpython/repl.py | 54 ++---------- bpython/simpleeval.py | 12 ++- bpython/translations/__init__.py | 21 +---- bpython/urwid.py | 66 +++------------ 16 files changed, 94 insertions(+), 467 deletions(-) delete mode 100644 bpython/_py3compat.py diff --git a/bpython/_py3compat.py b/bpython/_py3compat.py deleted file mode 100644 index 1a4c6d226..000000000 --- a/bpython/_py3compat.py +++ /dev/null @@ -1,36 +0,0 @@ -# The MIT License -# -# Copyright (c) 2012 the bpython authors. -# Copyright (c) 2015 Sebastian Ramacher -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - - -""" - Helper module for Python 3 compatibility. - - Defines the following attributes: - - - py3: True if the hosting Python runtime is of Python version 3 or later -""" - - -import sys - -py3 = sys.version_info[0] == 3 diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 2f46e7d3c..487372f87 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -37,13 +37,9 @@ from . import importcompletion from . import line as lineparts from .line import LinePart -from ._py3compat import py3 from .lazyre import LazyReCompile from .simpleeval import safe_eval, evaluate_current_expression, EvaluationError -if not py3: - from types import InstanceType, ClassType - # Autocomplete modes SIMPLE = "simple" @@ -109,10 +105,7 @@ ) ) -if py3: - KEYWORDS = frozenset(keyword.kwlist) -else: - KEYWORDS = frozenset(name.decode("ascii") for name in keyword.kwlist) +KEYWORDS = frozenset(keyword.kwlist) def after_last_dot(name): @@ -253,19 +246,8 @@ class FilenameCompletion(BaseCompletionType): def __init__(self, mode=SIMPLE): super(FilenameCompletion, self).__init__(False, mode) - if py3: - - def safe_glob(self, pathname): - return glob.iglob(glob.escape(pathname) + "*") - - else: - - def safe_glob(self, pathname): - try: - return glob.glob(pathname + "*") - except re.error: - # see #491 - return tuple() + def safe_glob(self, pathname): + return glob.iglob(glob.escape(pathname) + "*") def matches(self, cursor_offset, line, **kwargs): cs = lineparts.current_string(cursor_offset, line) @@ -366,10 +348,6 @@ def attr_lookup(self, obj, expr, attr): except ValueError: pass - if not py3 and isinstance(obj, (InstanceType, ClassType)): - # Account for the __dict__ in an old-style class. - words.append("__dict__") - matches = [] n = len(attr) for word in words: @@ -377,28 +355,11 @@ def attr_lookup(self, obj, expr, attr): matches.append("%s.%s" % (expr, word)) return matches - if py3: - - def list_attributes(self, obj): - # TODO: re-implement dir using getattr_static to avoid using - # AttrCleaner here? - with inspection.AttrCleaner(obj): - return dir(obj) - - else: - - def list_attributes(self, obj): - with inspection.AttrCleaner(obj): - if isinstance(obj, InstanceType): - try: - return dir(obj) - except Exception: - # This is a case where we can not prevent user code from - # running. We return a default list attributes on error - # instead. (#536) - return ["__doc__", "__module__"] - else: - return dir(obj) + def list_attributes(self, obj): + # TODO: re-implement dir using getattr_static to avoid using + # AttrCleaner here? + with inspection.AttrCleaner(obj): + return dir(obj) class DictKeyCompletion(BaseCompletionType): @@ -501,12 +462,11 @@ def matches(self, cursor_offset, line, **kwargs): for name in argspec[1][0] if isinstance(name, string_types) and name.startswith(r.word) ) - if py3: - matches.update( - name + "=" - for name in argspec[1][4] - if name.startswith(r.word) - ) + matches.update( + name + "=" + for name in argspec[1][4] + if name.startswith(r.word) + ) return matches if matches else None def locate(self, current_offset, line): diff --git a/bpython/cli.py b/bpython/cli.py index f83c7243f..1d4fbaeed 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -63,7 +63,6 @@ from pygments import format from pygments.formatters import TerminalFormatter from pygments.lexers import Python3Lexer -from ._py3compat import py3 from pygments.token import Token from .formatter import BPythonFormatter @@ -85,9 +84,6 @@ from .pager import page from .args import parse as argsparse -if not py3: - import inspect - # --- module globals --- stdscr = None @@ -220,10 +216,7 @@ def readline(self, size=-1): self.buffer.append(rest) buffer = buffer[:size] - if py3: - return buffer - else: - return buffer.encode(getpreferredencoding()) + return buffer def read(self, size=None): if size == 0: @@ -536,9 +529,6 @@ def echo(self, s, redraw=True): uses the formatting method as defined in formatter.py to parse the srings. It won't update the screen if it's reevaluating the code (as it does with undo).""" - if not py3 and isinstance(s, unicode): - s = s.encode(getpreferredencoding()) - a = get_colpair(self.config, "output") if "\x01" in s: rx = re.search("\x01([A-Za-z])([A-Za-z]?)", s) @@ -629,14 +619,11 @@ def get_key(self): while True: try: key += self.scr.getkey() - if py3: - # Seems like we get a in the locale's encoding - # encoded string in Python 3 as well, but of - # type str instead of bytes, hence convert it to - # bytes first and decode then - key = key.encode("latin-1").decode(getpreferredencoding()) - else: - key = key.decode(getpreferredencoding()) + # Seems like we get a in the locale's encoding + # encoded string in Python 3 as well, but of + # type str instead of bytes, hence convert it to + # bytes first and decode then + key = key.encode("latin-1").decode(getpreferredencoding()) self.scr.nodelay(False) except UnicodeDecodeError: # Yes, that actually kind of sucks, but I don't see another way to get @@ -726,9 +713,8 @@ def mkargspec(self, topline, in_arg, down): _args = topline.argspec.varargs _kwargs = topline.argspec.varkwargs is_bound_method = topline.is_bound_method - if py3: - kwonly = topline.argspec.kwonly - kwonly_defaults = topline.argspec.kwonly_defaults or dict() + kwonly = topline.argspec.kwonly + kwonly_defaults = topline.argspec.kwonly_defaults or dict() max_w = int(self.scr.getmaxyx()[1] * 0.6) self.list_win.erase() self.list_win.resize(3, max_w) @@ -776,13 +762,7 @@ def mkargspec(self, topline, in_arg, down): if k == in_arg or i == in_arg: color |= curses.A_BOLD - if not py3: - # See issue #138: We need to format tuple unpacking correctly - # We use the undocumented function inspection.strseq() for - # that. Fortunately, that madness is gone in Python 3. - self.list_win.addstr(inspect.strseq(i, str), color) - else: - self.list_win.addstr(str(i), color) + self.list_win.addstr(str(i), color) if kw is not None: self.list_win.addstr("=", punctuation_colpair) self.list_win.addstr(kw, get_colpair(self.config, "token")) @@ -796,7 +776,7 @@ def mkargspec(self, topline, in_arg, down): "*%s" % (_args,), get_colpair(self.config, "token") ) - if py3 and kwonly: + if kwonly: if not _args: if args: self.list_win.addstr(", ", punctuation_colpair) @@ -816,7 +796,7 @@ def mkargspec(self, topline, in_arg, down): ) if _kwargs: - if args or _args or (py3 and kwonly): + if args or _args or kwonly: self.list_win.addstr(", ", punctuation_colpair) self.list_win.addstr( "**%s" % (_kwargs,), get_colpair(self.config, "token") @@ -1094,10 +1074,7 @@ def prompt(self, more): self.echo( "\x01%s\x03%s" % (self.config.color_scheme["prompt"], self.ps1) ) - if py3: - self.stdout_hist += self.ps1 - else: - self.stdout_hist += self.ps1.encode(getpreferredencoding()) + self.stdout_hist += self.ps1 self.screen_hist.append( "\x01%s\x03%s\x04" % (self.config.color_scheme["prompt"], self.ps1) @@ -1105,10 +1082,7 @@ def prompt(self, more): else: prompt_more_color = self.config.color_scheme["prompt_more"] self.echo("\x01%s\x03%s" % (prompt_more_color, self.ps2)) - if py3: - self.stdout_hist += self.ps2 - else: - self.stdout_hist += self.ps2.encode(getpreferredencoding()) + self.stdout_hist += self.ps2 self.screen_hist.append( "\x01%s\x03%s\x04" % (prompt_more_color, self.ps2) ) @@ -1175,10 +1149,7 @@ def repl(self): self.history.append(inp) self.screen_hist[-1] += self.f_string - if py3: - self.stdout_hist += inp + "\n" - else: - self.stdout_hist += inp.encode(getpreferredencoding()) + "\n" + self.stdout_hist += inp + "\n" stdout_position = len(self.stdout_hist) self.more = self.push(inp) if not self.more: @@ -1241,10 +1212,7 @@ def reevaluate(self): self.iy, self.ix = self.scr.getyx() for line in self.history: - if py3: - self.stdout_hist += line + "\n" - else: - self.stdout_hist += line.encode(getpreferredencoding()) + "\n" + self.stdout_hist += line + "\n" self.print_line(line) self.screen_hist[-1] += self.f_string # I decided it was easier to just do this manually @@ -1278,9 +1246,6 @@ def write(self, s): else: t = s - if not py3 and isinstance(t, unicode): - t = t.encode(getpreferredencoding()) - if not self.stdout_hist: self.stdout_hist = t else: @@ -1390,16 +1355,12 @@ def lsize(): if v_items: self.list_win.addstr("\n ") - if not py3: - encoding = getpreferredencoding() for ix, i in enumerate(v_items): padding = (wl - len(i)) * " " if i == current_item: color = get_colpair(self.config, "operator") else: color = get_colpair(self.config, "main") - if not py3: - i = i.encode(encoding) self.list_win.addstr(i + padding, color) if (cols == 1 or (ix and not (ix + 1) % cols)) and ix + 1 < len( v_items @@ -1407,8 +1368,6 @@ def lsize(): self.list_win.addstr("\n ") if self.docstring is not None: - if not py3 and isinstance(docstring_string, unicode): - docstring_string = docstring_string.encode(encoding, "ignore") self.list_win.addstr( "\n" + docstring_string, get_colpair(self.config, "comment") ) @@ -1544,10 +1503,7 @@ def send_current_line_to_editor(self): self.iy, self.ix = self.scr.getyx() self.evaluating = True for line in lines: - if py3: - self.stdout_hist += line + "\n" - else: - self.stdout_hist += line.encode(getpreferredencoding()) + "\n" + self.stdout_hist += line + "\n" self.history.append(line) self.print_line(line) self.screen_hist[-1] += self.f_string @@ -1705,9 +1661,6 @@ def settext(self, s, c=None, p=False): self.c = c if s: - if not py3 and isinstance(s, unicode): - s = s.encode(getpreferredencoding()) - if self.c: self.win.addstr(s, self.c) else: @@ -1996,15 +1949,6 @@ def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): ) clirepl.write("\n") - if not py3: - # XXX these deprecation warnings need to go at some point - clirepl.write( - _( - "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." - ) - ) - clirepl.write("\n") - exit_value = clirepl.repl() if hasattr(sys, "exitfunc"): sys.exitfunc() diff --git a/bpython/curtsies.py b/bpython/curtsies.py index f550946a6..e1c4b7ba3 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -19,7 +19,6 @@ from .curtsiesfrontend import events as bpythonevents from . import inspection from .repl import extract_exit_value -from ._py3compat import py3 logger = logging.getLogger(__name__) @@ -199,14 +198,6 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): if banner is not None: print(banner) - if not py3: - # XXX these deprecation warnings need to go at some point - print( - _( - "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." - ) - ) - global repl repl = FullCurtsiesRepl(config, locals_, welcome_message, interp) try: diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 46ff1fa8c..65c5bffdd 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -23,7 +23,6 @@ import pydoc import bpython._internal -from bpython._py3compat import py3 from bpython.repl import getpreferredencoding @@ -47,8 +46,6 @@ def __init__(self, repl=None): super(_Helper, self).__init__() def pager(self, output): - if not py3 and isinstance(output, str): - output = output.decode(getpreferredencoding()) self._repl.pager(output) def __call__(self, *args, **kwargs): diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 0eceeb33e..da965db6b 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -16,7 +16,6 @@ import logging import threading -from bpython._py3compat import py3 from bpython.config import getpreferredencoding logger = logging.getLogger(__name__) @@ -221,8 +220,6 @@ def __init__(self, coderunner, on_write, real_fileobj): self._real_fileobj = real_fileobj def write(self, s, *args, **kwargs): - if not py3 and isinstance(s, str): - s = s.decode(getpreferredencoding(), "ignore") self.on_write(s, *args, **kwargs) return self.coderunner.request_from_main_context(force_refresh=True) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 31bc439e4..c2afccddf 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -8,15 +8,11 @@ import inspect from six import iteritems -from bpython._py3compat import py3 INDENT = 4 # TODO Allow user config of keybindings for these actions -if not py3: - getargspec = lambda func: inspect.getargspec(func)[0] -else: - getargspec = lambda func: inspect.signature(func).parameters +getargspec = lambda func: inspect.signature(func).parameters class AbstractEdits(object): diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9e8da8cff..f3b32e144 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -36,7 +36,6 @@ from bpython.formatter import BPythonFormatter from bpython import autocomplete from bpython.translations import _ -from bpython._py3compat import py3 from bpython.pager import get_pager_command from bpython.curtsiesfrontend import replpainter as paint @@ -56,11 +55,6 @@ from curtsies.configfile_keynames import keymap as key_dispatch -if not py3: - import imp - import pkgutil - - logger = logging.getLogger(__name__) INCONSISTENT_HISTORY_MSG = "#<---History inconsistent with output shown--->" @@ -98,10 +92,6 @@ # i.e. control characters like '' will be stripped MAX_EVENTS_POSSIBLY_NOT_PASTE = 20 -# This is needed for is_nop and should be removed once is_nop is fixed. -if py3: - unicode = str - class FakeStdin(object): """The stdin object user code will reference @@ -177,7 +167,7 @@ def add_input_character(self, e): assert len(e) == 1, "added multiple characters: %r" % e logger.debug("adding normal char %r to current line", e) - c = e if py3 else e.encode("utf8") + c = e self.current_line = ( self.current_line[: self.cursor_offset] + c @@ -254,20 +244,6 @@ def load_module(self, name): return module -if not py3: - # Remember that pkgutil.ImpLoader is an old style class. - class ImpImportLoader(pkgutil.ImpLoader): - def __init__(self, watcher, *args): - self.watcher = watcher - pkgutil.ImpLoader.__init__(self, *args) - - def load_module(self, name): - module = pkgutil.ImpLoader.load_module(self, name) - if hasattr(module, "__file__"): - self.watcher.track_module(module.__file__) - return module - - class ImportFinder(object): def __init__(self, watcher, old_meta_path): self.watcher = watcher @@ -304,15 +280,6 @@ def find_module(self, fullname, path=None): if loader is not None: return ImportLoader(self.watcher, loader) - if not py3: - # Python 2 does not have the default finders stored in - # sys.meta_path. Use imp to perform the actual importing. - try: - result = imp.find_module(fullname, path) - return ImpImportLoader(self.watcher, fullname, *result) - except ImportError: - return None - return None @@ -1998,7 +1965,7 @@ def focus_on_subprocess(self, args): def pager(self, text): """Runs an external pager on text - text must be a unicode""" + text must be a str""" command = get_pager_command() with tempfile.NamedTemporaryFile() as tmp: tmp.write(text.encode(getpreferredencoding())) @@ -2066,7 +2033,7 @@ def key_help_text(self): def is_nop(char): - return unicodedata.category(unicode(char)) == "Cc" + return unicodedata.category(str(char)) == "Cc" def tabs_to_spaces(line): diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 3211aab36..481ad67dd 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -6,10 +6,6 @@ from curtsies.fmtfuncs import bold from bpython.curtsiesfrontend.parse import func_for_letter -from bpython._py3compat import py3 - -if not py3: - import inspect logger = logging.getLogger(__name__) @@ -99,9 +95,8 @@ def formatted_argspec(funcprops, arg_pos, columns, config): _args = funcprops.argspec.varargs _kwargs = funcprops.argspec.varkwargs is_bound_method = funcprops.is_bound_method - if py3: - kwonly = funcprops.argspec.kwonly - kwonly_defaults = funcprops.argspec.kwonly_defaults or dict() + kwonly = funcprops.argspec.kwonly + kwonly_defaults = funcprops.argspec.kwonly_defaults or dict() arg_color = func_for_letter(config.color_scheme["name"]) func_color = func_for_letter(config.color_scheme["name"].swapcase()) @@ -126,15 +121,10 @@ def formatted_argspec(funcprops, arg_pos, columns, config): if i == arg_pos or arg == arg_pos: color = bolds[color] - if not py3: - s += color(inspect.strseq(arg, unicode)) - else: - s += color(arg) + s += color(arg) if kw is not None: s += punctuation_color("=") - if not py3: - kw = kw.decode("ascii", "replace") s += token_color(kw) if i != len(args) - 1: @@ -145,7 +135,7 @@ def formatted_argspec(funcprops, arg_pos, columns, config): s += punctuation_color(", ") s += token_color("*%s" % (_args,)) - if py3 and kwonly: + if kwonly: if not _args: if args: s += punctuation_color(", ") @@ -163,7 +153,7 @@ def formatted_argspec(funcprops, arg_pos, columns, config): s += token_color(repr(default)) if _kwargs: - if args or _args or (py3 and kwonly): + if args or _args or kwonly: s += punctuation_color(", ") s += token_color("**%s" % (_kwargs,)) s += punctuation_color(")") @@ -174,7 +164,7 @@ def formatted_argspec(funcprops, arg_pos, columns, config): def formatted_docstring(docstring, columns, config): if isinstance(docstring, bytes): docstring = docstring.decode("utf8") - elif isinstance(docstring, str if py3 else unicode): + elif isinstance(docstring, str): pass else: # TODO: fail properly here and catch possible exceptions in callers. diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 7114f01ae..97e02223d 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -21,7 +21,6 @@ # THE SOFTWARE. -from ._py3compat import py3 from .line import ( current_word, current_import, @@ -33,15 +32,9 @@ import os import sys import warnings +import importlib.machinery -if py3: - import importlib.machinery - - SUFFIXES = importlib.machinery.all_suffixes() -else: - import imp - - SUFFIXES = [suffix for suffix, mode, type in imp.get_suffixes()] +SUFFIXES = importlib.machinery.all_suffixes() # The cached list of all known modules modules = set() @@ -146,8 +139,7 @@ def find_modules(path): except EnvironmentError: filenames = [] - if py3: - finder = importlib.machinery.FileFinder(path) + finder = importlib.machinery.FileFinder(path) for name in filenames: if any(fnmatch.fnmatch(name, entry) for entry in skiplist): @@ -165,29 +157,21 @@ def find_modules(path): if name.endswith(suffix): name = name[: -len(suffix)] break - if py3 and name == "badsyntax_pep3120": + if name == "badsyntax_pep3120": # Workaround for issue #166 continue try: is_package = False with warnings.catch_warnings(): warnings.simplefilter("ignore", ImportWarning) - if py3: - spec = finder.find_spec(name) - if spec is None: - continue - if spec.submodule_search_locations is not None: - pathname = spec.submodule_search_locations[0] - is_package = True - else: - pathname = spec.origin + spec = finder.find_spec(name) + if spec is None: + continue + if spec.submodule_search_locations is not None: + pathname = spec.submodule_search_locations[0] + is_package = True else: - fo, pathname, _ = imp.find_module(name, [path]) - if fo is not None: - fo.close() - else: - # Yay, package - is_package = True + pathname = spec.origin except (ImportError, IOError, SyntaxError): continue except UnicodeEncodeError: diff --git a/bpython/inspection.py b/bpython/inspection.py index 088acdfe6..6c0370448 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -32,14 +32,8 @@ from pygments.lexers import Python3Lexer from types import MemberDescriptorType -from ._py3compat import py3 from .lazyre import LazyReCompile -if not py3: - import types - - _name = LazyReCompile(r"[a-zA-Z_]\w*$") - ArgSpec = namedtuple( "ArgSpec", [ @@ -107,17 +101,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): # /Dark magic -if py3: - - def is_new_style(obj): - return True - - -else: - - def is_new_style(obj): - """Returns True if obj is a new-style class or object""" - return type(obj) not in [types.InstanceType, types.ClassType] +def is_new_style(obj): + return True class _Repr(object): @@ -263,11 +248,7 @@ def getfuncprops(func, f): # '__init__' throws xmlrpclib.Fault (see #202) return None try: - if py3: - argspec = get_argspec_from_signature(f) - else: - argspec = list(inspect.getargspec(f)) - + argspec = get_argspec_from_signature(f) fixlongargs(f, argspec) if len(argspec) == 4: argspec = argspec + [list(), dict(), None] @@ -284,16 +265,10 @@ def getfuncprops(func, f): def is_eval_safe_name(string): - if py3: - return all( - part.isidentifier() and not keyword.iskeyword(part) - for part in string.split(".") - ) - else: - return all( - _name.match(part) and not keyword.iskeyword(part) - for part in string.split(".") - ) + return all( + part.isidentifier() and not keyword.iskeyword(part) + for part in string.split(".") + ) def is_callable(obj): @@ -389,37 +364,13 @@ def get_encoding_file(fname): return "ascii" -if not py3: - - def getattr_safe(obj, name): - """side effect free getattr""" - if not is_new_style(obj): - return getattr(obj, name) - - with AttrCleaner(obj): - to_look_through = ( - obj.__mro__ - if inspect.isclass(obj) - else (obj,) + type(obj).__mro__ - ) - for cls in to_look_through: - if hasattr(cls, "__dict__") and name in cls.__dict__: - result = cls.__dict__[name] - if isinstance(result, MemberDescriptorType): - result = getattr(obj, name) - return result - raise AttributeError(name) - - -else: - - def getattr_safe(obj, name): - """side effect free getattr (calls getattr_static).""" - result = inspect.getattr_static(obj, name) - # Slots are a MemberDescriptorType - if isinstance(result, MemberDescriptorType): - result = getattr(obj, name) - return result +def getattr_safe(obj, name): + """side effect free getattr (calls getattr_static).""" + result = inspect.getattr_static(obj, name) + # Slots are a MemberDescriptorType + if isinstance(result, MemberDescriptorType): + result = getattr(obj, name) + return result def hasattr_safe(obj, name): @@ -430,15 +381,6 @@ def hasattr_safe(obj, name): return False -if py3: - - def get_source_unicode(obj): - """Returns a decoded source of object""" - return inspect.getsource(obj) - - -else: - - def get_source_unicode(obj): - """Returns a decoded source of object""" - return inspect.getsource(obj).decode(get_encoding(obj)) +def get_source_unicode(obj): + """Returns a decoded source of object""" + return inspect.getsource(obj) diff --git a/bpython/pager.py b/bpython/pager.py index 97ea8f56c..98bfd0713 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -29,9 +29,6 @@ import sys import shlex -from bpython._py3compat import py3 - - def get_pager_command(default="less -rf"): command = shlex.split(os.environ.get("PAGER", default)) return command @@ -53,8 +50,7 @@ def page(data, use_internal=False): curses.endwin() try: popen = subprocess.Popen(command, stdin=subprocess.PIPE) - if py3 or isinstance(data, unicode): - data = data.encode(sys.__stdout__.encoding, "replace") + data = data.encode(sys.__stdout__.encoding, "replace") popen.stdin.write(data) popen.stdin.close() except OSError as e: diff --git a/bpython/repl.py b/bpython/repl.py index 02774b340..40ee4cc41 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -44,7 +44,6 @@ from . import autocomplete from . import inspection -from ._py3compat import py3 from .clipboard import get_clipboard, CopyFailed from .config import getpreferredencoding from .formatter import Parenthesis @@ -142,35 +141,12 @@ def runsource(self, source, filename=None, symbol="single", encode="auto"): would cause a syntax error to be thrown which would reference code the user did not write, setting encoding to True when source is a unicode string in Python 2 will throw a ValueError.""" - # str means bytestring in Py2 - if encode and not py3 and isinstance(source, unicode): - if encode != "auto": - raise ValueError("can't add encoding line to unicode input") - encode = False if encode and filename is not None: # files have encoding comments or implicit encoding of ASCII if encode != "auto": raise ValueError("shouldn't add encoding line to file contents") encode = False - if encode and not py3 and isinstance(source, str): - # encoding makes sense for bytestrings, so long as there - # isn't already an encoding comment - comment = inspection.get_encoding_comment(source) - if comment: - # keep the existing encoding comment, but add two lines - # because this interp always adds 2 to stack trace line - # numbers in Python 2 - source = source.replace(comment, b"%s\n\n" % comment, 1) - else: - source = b"# coding: %s\n\n%s" % (self.encoding, source) - elif not py3 and filename is None: - # 2 blank lines still need to be added - # because this interpreter always adds 2 to stack trace line - # numbers in Python 2 when the filename is "" - newlines = u"\n\n" if isinstance(source, unicode) else b"\n\n" - source = newlines + source - # we know we're in Python 2 here, so ok to reference unicode if filename is None: filename = filename_for_console_input(source) with self.timer: @@ -200,8 +176,6 @@ def showsyntaxerror(self, filename=None): # strip linecache line number if self.bpython_input_re.match(filename): filename = "" - if filename == "" and not py3: - lineno -= 2 value = SyntaxError(msg, (filename, lineno, offset, line)) sys.last_value = value exc_formatted = traceback.format_exception_only(exc_type, value) @@ -224,9 +198,6 @@ def showtraceback(self): if self.bpython_input_re.match(fname): fname = "" tblist[i] = (fname, lineno, module, something) - # Set the right lineno (encoding header adds an extra line) - if fname == "" and not py3: - tblist[i] = (fname, lineno - 2, module, something) l = traceback.format_list(tblist) if l: @@ -492,21 +463,14 @@ def __init__(self, interp, config): @property def ps1(self): try: - if not py3: - return sys.ps1.decode(getpreferredencoding()) - else: - return sys.ps1 + return sys.ps1 except AttributeError: - return u">>> " + return ">>> " @property def ps2(self): try: - if not py3: - return sys.ps2.decode(getpreferredencoding()) - else: - return sys.ps2 - + return sys.ps2 except AttributeError: return u"... " @@ -520,9 +484,6 @@ def startup(self): encoding = inspection.get_encoding_file(filename) with io.open(filename, "rt", encoding=encoding) as f: source = f.read() - if not py3: - # Early Python 2.7.X need bytes. - source = source.encode(encoding) self.interp.runsource(source, filename, "exec", encode=False) def current_string(self, concatenate=False): @@ -1177,10 +1138,7 @@ def send_to_external_editor(self, text): args = editor_args + [temp.name] if subprocess.call(args) == 0: with open(temp.name) as f: - if py3: - return f.read() - else: - return f.read().decode(encoding) + return f.read() else: return text @@ -1201,8 +1159,8 @@ def edit_config(self): default_config = pkgutil.get_data( "bpython", "sample-config" ) - if py3: # py3 files need unicode - default_config = default_config.decode("ascii") + # Py3 files need unicode + default_config = default_config.decode("ascii") containing_dir = os.path.dirname( os.path.abspath(self.config.config_path) ) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 21a040243..851de28e3 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -34,12 +34,11 @@ from six.moves import builtins from . import line as line_properties -from ._py3compat import py3 from .inspection import getattr_safe -_string_type_nodes = (ast.Str, ast.Bytes) if py3 else (ast.Str,) -_numeric_types = (int, float, complex) + (() if py3 else (long,)) -_name_type_nodes = (ast.Name, ast.NameConstant) if py3 else (ast.Name,) +_string_type_nodes = (ast.Str, ast.Bytes) +_numeric_types = (int, float, complex) +_name_type_nodes = (ast.Name, ast.NameConstant) class EvaluationError(Exception): @@ -58,9 +57,8 @@ def safe_eval(expr, namespace): # This function is under the Python License, Version 2 # This license requires modifications to the code be reported. -# Based on ast.literal_eval in Python 2 and Python 3 +# Based on ast.literal_eval # Modifications: -# * Python 2 and Python 3 versions of the function are combined # * checks that objects used as operands of + and - are numbers # instead of checking they are constructed with number literals # * new docstring describing different functionality @@ -90,7 +88,7 @@ def simple_eval(node_or_string, namespace=None): node_or_string = node_or_string.body def _convert(node): - if py3 and isinstance(node, ast.Constant): + if isinstance(node, ast.Constant): return node.value elif isinstance(node, _string_type_nodes): return node.s diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index c3e1b0bf7..e32957b4d 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -4,27 +4,14 @@ import sys from .. import package_dir -from .._py3compat import py3 translator = None -if py3: - - def _(message): - return translator.gettext(message) - - def ngettext(singular, plural, n): - return translator.ngettext(singular, plural, n) - - -else: - - def _(message): - return translator.ugettext(message) - - def ngettext(singular, plural, n): - return translator.ungettext(singular, plural, n) +def _(message): + return translator.gettext(message) +def ngettext(singular, plural, n): + return translator.ngettext(singular, plural, n) def init(locale_dir=None, languages=None): try: diff --git a/bpython/urwid.py b/bpython/urwid.py index 9667c05da..5b942bdbf 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -45,7 +45,6 @@ from pygments.token import Token from . import args as bpargs, repl, translations -from ._py3compat import py3 from .config import getpreferredencoding from .formatter import theme_map from .importcompletion import find_coroutine @@ -55,9 +54,6 @@ import urwid -if not py3: - import inspect - Parenthesis = Token.Punctuation.Parenthesis # Urwid colors are: @@ -600,7 +596,7 @@ def __init__(self, event_loop, palette, interpreter, config): self.tooltip = urwid.ListBox(urwid.SimpleListWalker([])) self.tooltip.grid = None self.overlay = Tooltip(self.listbox, self.tooltip) - self.stdout_hist = "" # native str (bytes in Py2, unicode in Py3) + self.stdout_hist = "" # native str (unicode in Py3) self.frame = urwid.Frame(self.overlay) @@ -780,13 +776,10 @@ def _populate_completion(self): func_name, args, is_bound = self.funcprops in_arg = self.arg_pos args, varargs, varkw, defaults = args[:4] - if py3: - kwonly = self.funcprops.argspec.kwonly - kwonly_defaults = ( - self.funcprops.argspec.kwonly_defaults or {} - ) - else: - kwonly, kwonly_defaults = [], {} + kwonly = self.funcprops.argspec.kwonly + kwonly_defaults = ( + self.funcprops.argspec.kwonly_defaults or {} + ) markup = [("bold name", func_name), ("name", ": (")] # the isinstance checks if we're in a positional arg @@ -810,13 +803,7 @@ def _populate_completion(self): if k == in_arg or i == in_arg: color = "bold " + color - if not py3: - # See issue #138: We need to format tuple unpacking correctly - # We use the undocumented function inspection.strseq() for - # that. Fortunately, that madness is gone in Python 3. - markup.append((color, inspect.strseq(i, str))) - else: - markup.append((color, str(i))) + markup.append((color, str(i))) if kw is not None: markup.extend([("punctuation", "="), ("token", kw)]) if k != len(args) - 1: @@ -912,12 +899,7 @@ def reevaluate(self): self.iy, self.ix = self.scr.getyx() for line in self.history: - if py3: - self.stdout_hist += line + "\n" - else: - self.stdout_hist += ( - line.encode(locale.getpreferredencoding()) + "\n" - ) + self.stdout_hist += line + "\n" self.print_line(line) # I decided it was easier to just do this manually # than to make the print_line and history stuff more flexible. @@ -950,9 +932,6 @@ def write(self, s): else: t = s - if not py3 and isinstance(t, unicode): - t = t.encode(locale.getpreferredencoding()) - if not self.stdout_hist: self.stdout_hist = t else: @@ -1013,16 +992,10 @@ def prompt(self, more): # caption is bytes this breaks typing non-ascii into bpython. if not more: caption = ("prompt", self.ps1) - if py3: - self.stdout_hist += self.ps1 - else: - self.stdout_hist += self.ps1.encode(getpreferredencoding()) + self.stdout_hist += self.ps1 else: caption = ("prompt_more", self.ps2) - if py3: - self.stdout_hist += self.ps2 - else: - self.stdout_hist += self.ps2.encode(getpreferredencoding()) + self.stdout_hist += self.ps2 self.edit = BPythonEdit(self.config, caption=caption) urwid.connect_signal(self.edit, "change", self.on_input_change) @@ -1067,10 +1040,7 @@ def handle_input(self, event): inp = self.edit.get_edit_text() self.history.append(inp) self.edit.make_readonly() - if py3: - self.stdout_hist += inp - else: - self.stdout_hist += inp.encode(locale.getpreferredencoding()) + self.stdout_hist += inp self.stdout_hist += "\n" self.edit = None # This may take a while, so force a redraw first: @@ -1372,12 +1342,7 @@ def start(main_loop, user_data): filename = os.environ.get("PYTHONSTARTUP") if filename and os.path.isfile(filename): with open(filename, "r") as f: - if py3: - interpreter.runsource(f.read(), filename, "exec") - else: - interpreter.runsource( - f.read(), filename, "exec", encode=False - ) + interpreter.runsource(f.read(), filename, "exec") if banner is not None: myrepl.write(banner) @@ -1391,15 +1356,6 @@ def start(main_loop, user_data): ) myrepl.write("\n") - if not py3: - # XXX these deprecation warnings need to go at some point - myrepl.write( - _( - "WARNING: You are using `bpython` on Python 2. Support for Python 2 has been deprecated in version 0.19 and might disappear in a future version." - ) - ) - myrepl.write("\n") - myrepl.start() # This bypasses main_loop.set_alarm_in because we must *not* From b4424a3791c68ab0b2d94908d782aef21dfb5b92 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 18:43:43 +0200 Subject: [PATCH 1024/1650] Remove bpython.inspection.is_new_style which always returns True --- bpython/inspection.py | 31 +++++++++++++------------------ bpython/test/test_inspection.py | 8 -------- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 6c0370448..f37c7b6fc 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -72,20 +72,19 @@ def __enter__(self): # original methods. :-( # The upshot being that introspecting on an object to display its # attributes will avoid unwanted side-effects. - if is_new_style(self.obj): - __getattr__ = getattr(type_, "__getattr__", None) - if __getattr__ is not None: - try: - setattr(type_, "__getattr__", (lambda *_, **__: None)) - except TypeError: - __getattr__ = None - __getattribute__ = getattr(type_, "__getattribute__", None) - if __getattribute__ is not None: - try: - setattr(type_, "__getattribute__", object.__getattribute__) - except TypeError: - # XXX: This happens for e.g. built-in types - __getattribute__ = None + __getattr__ = getattr(type_, "__getattr__", None) + if __getattr__ is not None: + try: + setattr(type_, "__getattr__", (lambda *_, **__: None)) + except TypeError: + __getattr__ = None + __getattribute__ = getattr(type_, "__getattribute__", None) + if __getattribute__ is not None: + try: + setattr(type_, "__getattribute__", object.__getattribute__) + except TypeError: + # XXX: This happens for e.g. built-in types + __getattribute__ = None self.attribs = (__getattribute__, __getattr__) # /Dark magic @@ -101,10 +100,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): # /Dark magic -def is_new_style(obj): - return True - - class _Repr(object): """ Helper for `fixlongargs()`: Returns the given value in `__repr__()`. diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 991824f48..09b4943a1 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -57,14 +57,6 @@ def test_is_callable(self): self.assertFalse(inspection.is_callable(None)) self.assertTrue(inspection.is_callable(CallableMethod().method)) - def test_is_new_style_py3(self): - self.assertTrue(inspection.is_new_style(spam)) - self.assertTrue(inspection.is_new_style(Noncallable)) - self.assertTrue(inspection.is_new_style(OldNoncallable)) - self.assertTrue(inspection.is_new_style(Noncallable())) - self.assertTrue(inspection.is_new_style(OldNoncallable())) - self.assertTrue(inspection.is_new_style(None)) - def test_parsekeywordpairs(self): # See issue #109 def fails(spam=["-a", "-b"]): From 8f03ee2209f54f32473b6f58ea350cce39e25528 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 18:50:00 +0200 Subject: [PATCH 1025/1650] Update use of super and class definitions --- bpython/_internal.py | 2 +- bpython/autocomplete.py | 8 ++++---- bpython/cli.py | 12 ++++++------ bpython/clipboard.py | 4 ++-- bpython/config.py | 2 +- bpython/curtsies.py | 2 +- bpython/curtsiesfrontend/_internal.py | 8 ++++---- bpython/curtsiesfrontend/coderunner.py | 8 ++++---- bpython/curtsiesfrontend/events.py | 2 +- bpython/curtsiesfrontend/interaction.py | 2 +- bpython/curtsiesfrontend/interpreter.py | 4 ++-- bpython/curtsiesfrontend/manual_readline.py | 4 ++-- bpython/curtsiesfrontend/repl.py | 10 +++++----- bpython/filelock.py | 8 ++++---- bpython/formatter.py | 2 +- bpython/history.py | 2 +- bpython/inspection.py | 4 ++-- bpython/keys.py | 2 +- bpython/lazyre.py | 2 +- bpython/paste.py | 4 ++-- bpython/patch_linecache.py | 8 ++++---- bpython/repl.py | 12 ++++++------ bpython/test/fodder/original.py | 2 +- bpython/test/fodder/processed.py | 2 +- bpython/test/test_autocomplete.py | 8 ++++---- bpython/test/test_crashers.py | 4 ++-- bpython/test/test_curtsies.py | 2 +- bpython/test/test_curtsies_painting.py | 2 +- bpython/test/test_inspection.py | 18 +++++++++--------- bpython/test/test_manual_readline.py | 2 +- bpython/test/test_simpleeval.py | 4 ++-- bpython/urwid.py | 2 +- 32 files changed, 79 insertions(+), 79 deletions(-) diff --git a/bpython/_internal.py b/bpython/_internal.py index e1609b8cb..4545862c4 100644 --- a/bpython/_internal.py +++ b/bpython/_internal.py @@ -7,7 +7,7 @@ pydoc.pager = page -class _Helper(object): +class _Helper: def __init__(self): if hasattr(pydoc.Helper, "output"): # See issue #228 diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 487372f87..5ad067110 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -149,7 +149,7 @@ def method_match_fuzzy(word, size, text): } -class BaseCompletionType(object): +class BaseCompletionType: """Describes different completion types""" def __init__(self, shown_before_tab=True, mode=SIMPLE): @@ -209,7 +209,7 @@ def __init__(self, completers, mode=SIMPLE): ) self._completers = completers - super(CumulativeCompleter, self).__init__(True, mode) + super().__init__(True, mode) def locate(self, current_offset, line): return self._completers[0].locate(current_offset, line) @@ -244,7 +244,7 @@ def format(self, word): class FilenameCompletion(BaseCompletionType): def __init__(self, mode=SIMPLE): - super(FilenameCompletion, self).__init__(False, mode) + super().__init__(False, mode) def safe_glob(self, pathname): return glob.iglob(glob.escape(pathname) + "*") @@ -567,7 +567,7 @@ def matches(self, cursor_offset, line, **kwargs): cursor_offset, line, ) - results = super(MultilineJediCompletion, self).matches( + results = super().matches( cursor_offset, line, history=history ) return results diff --git a/bpython/cli.py b/bpython/cli.py index 1d4fbaeed..1f1a99e08 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -121,7 +121,7 @@ def newfunc(self, *args, **kwargs): return newfunc -class FakeStream(object): +class FakeStream: """Provide a fake file object which calls functions on the interface provided.""" @@ -147,7 +147,7 @@ def flush(self): self.interface.flush() -class FakeStdin(object): +class FakeStdin: """Provide a fake stdin type for things like raw_input() etc.""" def __init__(self, interface): @@ -303,7 +303,7 @@ def make_colors(config): class CLIInteraction(repl.Interaction): def __init__(self, config, statusbar=None): - super(CLIInteraction, self).__init__(config, statusbar) + super().__init__(config, statusbar) def confirm(self, q): """Ask for yes or no and return boolean""" @@ -323,7 +323,7 @@ def file_prompt(self, s): class CLIRepl(repl.Repl): def __init__(self, scr, interp, statusbar, config, idle=None): - super(CLIRepl, self).__init__(interp, config) + super().__init__(interp, config) self.interp.writetb = self.writetb self.scr = scr self.stdout_hist = "" # native str (bytes in Py2, unicode in Py3) @@ -1527,7 +1527,7 @@ def send_current_line_to_editor(self): return "" -class Statusbar(object): +class Statusbar: """This class provides the status bar at the bottom of the screen. It has message() and prompt() methods for user interactivity, as well as settext() and clear() methods for changing its appearance. @@ -1821,7 +1821,7 @@ def do_resize(caller): # The list win resizes itself every time it appears so no need to do it here. -class FakeDict(object): +class FakeDict: """Very simple dict-alike that returns a constant value for any key - used as a hacky solution to using a colours dict containing colour codes if colour initialisation fails.""" diff --git a/bpython/clipboard.py b/bpython/clipboard.py index 555c15fe8..be17566f4 100644 --- a/bpython/clipboard.py +++ b/bpython/clipboard.py @@ -31,7 +31,7 @@ class CopyFailed(Exception): pass -class XClipboard(object): +class XClipboard: """Manage clipboard with xclip.""" def copy(self, content): @@ -43,7 +43,7 @@ def copy(self, content): raise CopyFailed() -class OSXClipboard(object): +class OSXClipboard: """Manage clipboard with pbcopy.""" def copy(self, content): diff --git a/bpython/config.py b/bpython/config.py index df88ec4e6..fa36132e9 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -8,7 +8,7 @@ from .autocomplete import SIMPLE as default_completion, ALL_MODES -class Struct(object): +class Struct: """Simple class for instantiating objects we can add arbitrary attributes to and use for various arbitrary things.""" diff --git a/bpython/curtsies.py b/bpython/curtsies.py index e1c4b7ba3..9b47cf44d 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -59,7 +59,7 @@ def __init__(self, config, locals_, banner, interp=None): with self.input_generator: pass # temp hack to get .original_stty - super(FullCurtsiesRepl, self).__init__( + super().__init__( locals_=locals_, config=config, banner=banner, diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 65c5bffdd..b52a47542 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -26,7 +26,7 @@ from bpython.repl import getpreferredencoding -class NopPydocPager(object): +class NopPydocPager: def __enter__(self): self._orig_pager = pydoc.pager pydoc.pager = self @@ -43,7 +43,7 @@ def __init__(self, repl=None): self._repl = repl pydoc.pager = self.pager - super(_Helper, self).__init__() + super().__init__() def pager(self, output): self._repl.pager(output) @@ -51,9 +51,9 @@ def pager(self, output): def __call__(self, *args, **kwargs): if self._repl.reevaluating: with NopPydocPager(): - return super(_Helper, self).__call__(*args, **kwargs) + return super().__call__(*args, **kwargs) else: - return super(_Helper, self).__call__(*args, **kwargs) + return super().__call__(*args, **kwargs) # vim: sw=4 ts=4 sts=4 ai et diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index da965db6b..ee62e7c6e 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -25,7 +25,7 @@ def is_main_thread(): return threading.main_thread() == threading.current_thread() -class SigintHappened(object): +class SigintHappened: """If this class is returned, a SIGINT happened while the main greenlet""" @@ -34,7 +34,7 @@ class SystemExitFromCodeRunner(SystemExit): greenlet""" -class RequestFromCodeRunner(object): +class RequestFromCodeRunner: """Message from the code runner""" @@ -61,7 +61,7 @@ def __init__(self, args): self.args = args -class CodeRunner(object): +class CodeRunner: """Runs user code in an interpreter. Running code requests a refresh by calling @@ -206,7 +206,7 @@ def request_from_main_context(self, force_refresh=False): return value -class FakeOutput(object): +class FakeOutput: def __init__(self, coderunner, on_write, real_fileobj): """Fakes sys.stdout or sys.stderr diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index 9980f7be1..fc0ef53c4 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -28,7 +28,7 @@ class ScheduledRefreshRequestEvent(curtsies.events.ScheduledEvent): for a few seconds""" def __init__(self, when): - super(ScheduledRefreshRequestEvent, self).__init__(when) + super().__init__(when) def __repr__(self): return "" % ( diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 5bcd51fb0..abd6009c6 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -47,7 +47,7 @@ def __init__( self.request_refresh = request_refresh self.schedule_refresh = schedule_refresh - super(StatusBar, self).__init__(config) + super().__init__(config) def push_permanent_message(self, msg): self._message = "" diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index cbfd982c5..702b21387 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -47,7 +47,7 @@ def __init__(self, color_scheme, **options): self.f_strings = {} for k, v in iteritems(color_scheme): self.f_strings[k] = "\x01%s" % (v,) - super(BPythonFormatter, self).__init__(**options) + super().__init__(**options) def format(self, tokensource, outfile): o = "" @@ -66,7 +66,7 @@ def __init__(self, locals=None, encoding=None): We include an argument for the outfile to pass to the formatter for it to write to. """ - super(Interp, self).__init__(locals, encoding) + super().__init__(locals, encoding) # typically changed after being instantiated # but used when interpreter used corresponding REPL diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index c2afccddf..621fc6740 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -15,7 +15,7 @@ getargspec = lambda func: inspect.signature(func).parameters -class AbstractEdits(object): +class AbstractEdits: default_kwargs = { "line": "hello world", @@ -160,7 +160,7 @@ def __init__( self.cut_buffer_edits = dict(cut_buffer_edits) for attr, func in awaiting_config.items(): for key in key_dispatch[getattr(config, attr)]: - super(ConfiguredEdits, self).add(key, func, overwrite=True) + super().add(key, func, overwrite=True) def add_config_attr(self, config_attr, func): raise NotImplementedError("Config already set on this mapping") diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f3b32e144..d93b6c4cb 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -93,7 +93,7 @@ MAX_EVENTS_POSSIBLY_NOT_PASTE = 20 -class FakeStdin(object): +class FakeStdin: """The stdin object user code will reference In user code, sys.stdin.read() asks the user for interactive input, @@ -214,7 +214,7 @@ def encoding(self): # TODO write a read() method? -class ReevaluateFakeStdin(object): +class ReevaluateFakeStdin: """Stdin mock used during reevaluation (undo) so raw_inputs don't have to be reentered""" @@ -232,7 +232,7 @@ def readline(self): return value -class ImportLoader(object): +class ImportLoader: def __init__(self, watcher, loader): self.watcher = watcher self.loader = loader @@ -244,7 +244,7 @@ def load_module(self, name): return module -class ImportFinder(object): +class ImportFinder: def __init__(self, watcher, old_meta_path): self.watcher = watcher self.old_meta_path = old_meta_path @@ -359,7 +359,7 @@ def __init__( ) self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch) logger.debug("starting parent init") - super(BaseRepl, self).__init__(interp, config) + super().__init__(interp, config) self.formatter = BPythonFormatter(config.color_scheme) diff --git a/bpython/filelock.py b/bpython/filelock.py index 64d6f1353..30bc0684d 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -38,7 +38,7 @@ has_msvcrt = False -class BaseLock(object): +class BaseLock: """Base class for file locking """ @@ -70,7 +70,7 @@ class UnixFileLock(BaseLock): """ def __init__(self, fileobj, mode=None, filename=None): - super(UnixFileLock, self).__init__(fileobj) + super().__init__(fileobj) if mode is None: mode = fcntl.LOCK_EX @@ -94,8 +94,8 @@ class WindowsFileLock(BaseLock): """ def __init__(self, fileobj, mode=None, filename=None): - super(WindowsFileLock, self).__init__(None) - self.filename = "{}.lock".format(filename) + super().__init__(None) + self.filename = f"{filename}.lock" def acquire(self): # create a lock file and lock it diff --git a/bpython/formatter.py b/bpython/formatter.py index 56196df33..872915b46 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -105,7 +105,7 @@ def __init__(self, color_scheme, **options): # FIXME: Find a way to make this the inverse of the current # background colour self.f_strings[k] += "I" - super(BPythonFormatter, self).__init__(**options) + super().__init__(**options) def format(self, tokensource, outfile): o = "" diff --git a/bpython/history.py b/bpython/history.py index 8ecae469e..0fb29d4d4 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -30,7 +30,7 @@ from .filelock import FileLock -class History(object): +class History: """Stores readline-style history and current place in it""" def __init__(self, entries=None, duplicates=True, hist_size=100): diff --git a/bpython/inspection.py b/bpython/inspection.py index f37c7b6fc..918d26ce2 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -50,7 +50,7 @@ FuncProps = namedtuple("FuncProps", ["func", "argspec", "is_bound_method"]) -class AttrCleaner(object): +class AttrCleaner: """A context manager that tries to make an object not exhibit side-effects on attribute lookup.""" @@ -100,7 +100,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): # /Dark magic -class _Repr(object): +class _Repr: """ Helper for `fixlongargs()`: Returns the given value in `__repr__()`. """ diff --git a/bpython/keys.py b/bpython/keys.py index 46e72493f..86984db6b 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -24,7 +24,7 @@ import string -class KeyMap(object): +class KeyMap: def __init__(self, default=""): self.map = {} self.default = default diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 49af626de..588558b85 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -24,7 +24,7 @@ import re -class LazyReCompile(object): +class LazyReCompile: """Compile regular expressions on first use This class allows one to store regular expressions and compiles them on diff --git a/bpython/paste.py b/bpython/paste.py index 72762fb56..7218c1049 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -36,7 +36,7 @@ class PasteFailed(Exception): pass -class PastePinnwand(object): +class PastePinnwand: def __init__(self, url, expiry): self.url = url self.expiry = expiry @@ -64,7 +64,7 @@ def paste(self, s): return (paste_url, removal_url) -class PasteHelper(object): +class PasteHelper: def __init__(self, executable): self.executable = executable diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index dba099c32..daf7251d9 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -6,7 +6,7 @@ class BPythonLinecache(dict): to also remember (in an unerasable way) bpython console input.""" def __init__(self, *args, **kwargs): - super(BPythonLinecache, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.bpython_history = [] def is_bpython_filename(self, fname): @@ -37,7 +37,7 @@ def remember_bpython_input(self, source): def __getitem__(self, key): if self.is_bpython_filename(key): return self.get_bpython_history(key) - return super(BPythonLinecache, self).__getitem__(key) + return super().__getitem__(key) def __contains__(self, key): if self.is_bpython_filename(key): @@ -46,11 +46,11 @@ def __contains__(self, key): return True except KeyError: return False - return super(BPythonLinecache, self).__contains__(key) + return super().__contains__(key) def __delitem__(self, key): if not self.is_bpython_filename(key): - return super(BPythonLinecache, self).__delitem__(key) + return super().__delitem__(key) def _bpython_clear_linecache(): diff --git a/bpython/repl.py b/bpython/repl.py index 40ee4cc41..e29a0d8cf 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -55,7 +55,7 @@ from . import simpleeval -class RuntimeTimer(object): +class RuntimeTimer: """Calculate running time""" def __init__(self): @@ -78,7 +78,7 @@ def estimate(self): return self.running_time - self.last_command -class Interpreter(code.InteractiveInterpreter, object): +class Interpreter(code.InteractiveInterpreter): """Source code interpreter for use in bpython.""" bpython_input_re = LazyReCompile(r"") @@ -215,7 +215,7 @@ def writetb(self, lines): self.write(line) -class MatchesIterator(object): +class MatchesIterator: """Stores a list of matches and which one is currently selected if any. Also responsible for doing the actual replacement of the original line with @@ -336,7 +336,7 @@ def clear(self): self.index = -1 -class Interaction(object): +class Interaction: def __init__(self, config, statusbar=None): self.config = config @@ -357,7 +357,7 @@ class SourceNotFound(Exception): """Exception raised when the requested source could not be found.""" -class LineTypeTranslator(object): +class LineTypeTranslator: """ Used when adding a tuple to all_logical_lines, to get input / output values having to actually type/know the strings """ @@ -367,7 +367,7 @@ class LineTypeTranslator(object): OUTPUT = "output" -class Repl(object): +class Repl: """Implements the necessary guff for a Python-repl-alike interface The execution of the code entered and all that stuff was taken from the diff --git a/bpython/test/fodder/original.py b/bpython/test/fodder/original.py index 1afd64129..d644c16dd 100644 --- a/bpython/test/fodder/original.py +++ b/bpython/test/fodder/original.py @@ -1,7 +1,7 @@ # careful: whitespace is very important in this file # also, this code runs - so everything should be a noop -class BlankLineBetweenMethods(object): +class BlankLineBetweenMethods: def method1(self): pass diff --git a/bpython/test/fodder/processed.py b/bpython/test/fodder/processed.py index 6b6331662..9f944ee66 100644 --- a/bpython/test/fodder/processed.py +++ b/bpython/test/fodder/processed.py @@ -1,6 +1,6 @@ #careful! Whitespace is very important in this file -class BlankLineBetweenMethods(object): +class BlankLineBetweenMethods: def method1(self): pass diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 40330c77a..a59c8a3a6 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -171,7 +171,7 @@ def test_formatting_takes_just_last_part(self): self.assertEqual(self.completer.format("/hello/there"), "there") -class MockNumPy(object): +class MockNumPy: """This is a mock numpy object that raises an error when there is an attempt to convert it to a boolean.""" @@ -211,7 +211,7 @@ def test_obj_that_does_not_allow_conversion_to_bool(self): self.assertEqual(com.matches(7, "mNumPy[", locals_=local), None) -class Foo(object): +class Foo: a = 10 def __init__(self): @@ -237,7 +237,7 @@ def asserts_when_called(self): raise AssertionError("getter method called") -class Slots(object): +class Slots: __slots__ = ["a", "b"] @@ -290,7 +290,7 @@ def test_att_matches_found_on_instance(self): ) def test_other_getitem_methods_not_called(self): - class FakeList(object): + class FakeList: def __getitem__(inner_self, i): self.fail("possibly side-effecting __getitem_ method called") diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index ef61322dc..39fce35cb 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -16,7 +16,7 @@ from twisted.trial.unittest import TestCase as TrialTestCase except ImportError: - class TrialTestCase(object): + class TrialTestCase: pass reactor = None @@ -44,7 +44,7 @@ def set_win_size(fd, rows, columns): fcntl.ioctl(fd, termios.TIOCSWINSZ, s) -class CrashersTest(object): +class CrashersTest: backend = "cli" def run_bpython(self, input): diff --git a/bpython/test/test_curtsies.py b/bpython/test/test_curtsies.py index cc8619e2f..d27e53dac 100644 --- a/bpython/test/test_curtsies.py +++ b/bpython/test/test_curtsies.py @@ -9,7 +9,7 @@ ScheduledEvent = namedtuple("ScheduledEvent", ["when", "event"]) -class EventGenerator(object): +class EventGenerator: def __init__(self, initial_events=(), scheduled_events=()): self._events = [] self._current_tick = 0 diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index b5947303f..47b1b5602 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -684,7 +684,7 @@ def test_472(self): def completion_target(num_names, chars_in_first_name=1): - class Class(object): + class Class: pass if chars_in_first_name < 1: diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 09b4943a1..a69a1b1db 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -23,7 +23,7 @@ def __call__(self): pass -class Callable(object): +class Callable: def __call__(self): pass @@ -32,7 +32,7 @@ class OldNoncallable: pass -class Noncallable(object): +class Noncallable: pass @@ -40,7 +40,7 @@ def spam(): pass -class CallableMethod(object): +class CallableMethod: def method(self): pass @@ -134,7 +134,7 @@ def test_get_source_file(self): self.assertEqual(encoding, "utf-8") -class A(object): +class A: a = "a" @@ -142,13 +142,13 @@ class B(A): b = "b" -class Property(object): +class Property: @property def prop(self): raise AssertionError("Property __get__ executed") -class Slots(object): +class Slots: __slots__ = ["s1", "s2", "s3"] @@ -158,21 +158,21 @@ def s4(self): raise AssertionError("Property __get__ executed") -class OverriddenGetattr(object): +class OverriddenGetattr: def __getattr__(self, attr): raise AssertionError("custom __getattr__ executed") a = 1 -class OverriddenGetattribute(object): +class OverriddenGetattribute: def __getattribute__(self, attr): raise AssertionError("custom __getattribute__ executed") a = 1 -class OverriddenMRO(object): +class OverriddenMRO: def __mro__(self): raise AssertionError("custom mro executed") diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 2218831c1..7b0936ae1 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -322,7 +322,7 @@ def g(cursor_offset, line): self.edits.add_config_attr("att", f) self.assertNotIn("att", self.edits) - class config(object): + class config: att = "c" key_dispatch = {"c": "c"} diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index f398e00ea..3c4d11b43 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -39,7 +39,7 @@ def test_name_lookup_indexing(self): self.assertEqual(simple_eval("a[b]", {"a": {"c": 1}, "b": "c"}), 1) def test_lookup_on_suspicious_types(self): - class FakeDict(object): + class FakeDict: pass with self.assertRaises(ValueError): @@ -92,7 +92,7 @@ def test_nonexistant_names_raise(self): simple_eval("a") def test_attribute_access(self): - class Foo(object): + class Foo: abc = 1 self.assertEqual(simple_eval("foo.abc", {"foo": Foo()}), 1) diff --git a/bpython/urwid.py b/bpython/urwid.py index 5b942bdbf..eef0ee1b9 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -169,7 +169,7 @@ def keypress(self, size, key): urwid.register_signal(StatusbarEdit, "prompt_enter") -class Statusbar(object): +class Statusbar: """Statusbar object, ripped off from bpython.cli. From 72822dd03150fd7a7efcc9132440c18b8d3ad265 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 18:50:31 +0200 Subject: [PATCH 1026/1650] Remove Python 2 specific tests --- bpython/test/test_inspection.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index a69a1b1db..c41361478 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -18,20 +18,11 @@ ''' -class OldCallable: - def __call__(self): - pass - - class Callable: def __call__(self): pass -class OldNoncallable: - pass - - class Noncallable: pass @@ -50,10 +41,7 @@ def test_is_callable(self): self.assertTrue(inspection.is_callable(spam)) self.assertTrue(inspection.is_callable(Callable)) self.assertTrue(inspection.is_callable(Callable())) - self.assertTrue(inspection.is_callable(OldCallable)) - self.assertTrue(inspection.is_callable(OldCallable())) self.assertFalse(inspection.is_callable(Noncallable())) - self.assertFalse(inspection.is_callable(OldNoncallable())) self.assertFalse(inspection.is_callable(None)) self.assertTrue(inspection.is_callable(CallableMethod().method)) From 68ffad8e2b1b98601a58a0cf968a97c29c229f30 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 19:16:41 +0200 Subject: [PATCH 1027/1650] Remove use of six.moves.range --- bpython/urwid.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index eef0ee1b9..db2efda4b 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -39,7 +39,6 @@ import locale import signal from optparse import Option -from six.moves import range from six import iteritems, string_types from pygments.token import Token From c829a836179301d9d2ab20893321014c6a4acba0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 19:20:17 +0200 Subject: [PATCH 1028/1650] Remove use of six.string_types --- bpython/autocomplete.py | 4 ++-- bpython/simpleeval.py | 5 ++--- bpython/urwid.py | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 5ad067110..e90865e15 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -31,7 +31,7 @@ import re import rlcompleter from six.moves import builtins -from six import string_types, iteritems +from six import iteritems from . import inspection from . import importcompletion @@ -460,7 +460,7 @@ def matches(self, cursor_offset, line, **kwargs): matches = set( name + "=" for name in argspec[1][0] - if isinstance(name, string_types) and name.startswith(r.word) + if isinstance(name, str) and name.startswith(r.word) ) matches.update( name + "=" diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 851de28e3..28b98538f 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -30,7 +30,6 @@ import ast import inspect import sys -from six import string_types from six.moves import builtins from . import line as line_properties @@ -82,7 +81,7 @@ def simple_eval(node_or_string, namespace=None): """ if namespace is None: namespace = {} - if isinstance(node_or_string, string_types): + if isinstance(node_or_string, str): node_or_string = ast.parse(node_or_string, mode="eval") if isinstance(node_or_string, ast.Expression): node_or_string = node_or_string.body @@ -179,7 +178,7 @@ def _convert(node): def safe_getitem(obj, index): """ Safely tries to access obj[index] """ - if type(obj) in (list, tuple, dict, bytes) + string_types: + if type(obj) in (list, tuple, dict, bytes, str): try: return obj[index] except (KeyError, IndexError): diff --git a/bpython/urwid.py b/bpython/urwid.py index db2efda4b..12f7cfb28 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -39,7 +39,7 @@ import locale import signal from optparse import Option -from six import iteritems, string_types +from six import iteritems from pygments.token import Token @@ -278,7 +278,7 @@ def decoding_input_filter(keys, raw): encoding = locale.getpreferredencoding() converted_keys = list() for key in keys: - if isinstance(key, string_types): + if isinstance(key, str): converted_keys.append(key.decode(encoding)) else: converted_keys.append(key) From e7cab9da76e017229bc2a1667489501824443582 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 20:31:06 +0200 Subject: [PATCH 1029/1650] Run setup with Python 3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d87ba8135..3730b1692 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os import platform From af54f72eaa185e0c819daa80f08e335a31cd982f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 20:42:17 +0200 Subject: [PATCH 1030/1650] Remove use of six.moves.builtins --- bpython/autocomplete.py | 2 +- bpython/curtsiesfrontend/sitefix.py | 3 +-- bpython/simpleeval.py | 2 +- bpython/test/__init__.py | 2 +- bpython/urwid.py | 3 +-- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index e90865e15..0496fb3b2 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -30,8 +30,8 @@ import os import re import rlcompleter -from six.moves import builtins from six import iteritems +import builtins from . import inspection from . import importcompletion diff --git a/bpython/curtsiesfrontend/sitefix.py b/bpython/curtsiesfrontend/sitefix.py index e423d6c06..5d33920cb 100644 --- a/bpython/curtsiesfrontend/sitefix.py +++ b/bpython/curtsiesfrontend/sitefix.py @@ -1,6 +1,5 @@ import sys - -from six.moves import builtins +import builtins def resetquit(builtins): diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 28b98538f..7fa2a2f00 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -30,7 +30,7 @@ import ast import inspect import sys -from six.moves import builtins +import builtins from . import line as line_properties from .inspection import getattr_safe diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index 90b97729c..d1e21380a 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -1,8 +1,8 @@ import unittest +import builtins from unittest import mock from bpython.translations import init -from six.moves import builtins import os diff --git a/bpython/urwid.py b/bpython/urwid.py index 12f7cfb28..116ef2b8f 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -31,8 +31,7 @@ This is still *VERY* rough. """ - - +import builtins import sys import os import time From 108e9c3ef4a9a93f40495e54df1c2ee6c1fe2985 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 20:42:59 +0200 Subject: [PATCH 1031/1650] Remove use of six.moves.configparser --- bpython/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index fa36132e9..345a639cb 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -3,7 +3,7 @@ import locale from itertools import chain from six import iterkeys, iteritems -from six.moves.configparser import ConfigParser +from configparser import ConfigParser from .autocomplete import SIMPLE as default_completion, ALL_MODES From 88511874a39ac54c62d25ead4224d4d1b8f34000 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 20:44:27 +0200 Subject: [PATCH 1032/1650] Remove use of six.moves.urllib_parse --- bpython/paste.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/paste.py b/bpython/paste.py index 7218c1049..736adea3f 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -22,7 +22,7 @@ from locale import getpreferredencoding -from six.moves.urllib_parse import quote as urlquote, urljoin, urlparse +from urllib.parse import quote as urlquote, urljoin, urlparse from string import Template import errno import requests From 0dfc03487ff9efc048d2b06150b011c61f78b0fd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 20:45:48 +0200 Subject: [PATCH 1033/1650] Remove use of six.moves.StringIO --- bpython/test/test_curtsies_repl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 885856c9b..fb6e9aa09 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -5,7 +5,6 @@ import io from functools import partial from contextlib import contextmanager -from six.moves import StringIO from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter @@ -222,7 +221,7 @@ def test_list_win_not_visible_and_match_selected_if_one_option(self): # from http://stackoverflow.com/a/17981937/398212 - thanks @rkennedy @contextmanager def captured_output(): - new_out, new_err = StringIO(), StringIO() + new_out, new_err = io.StringIO(), io.StringIO() old_out, old_err = sys.stdout, sys.stderr try: sys.stdout, sys.stderr = new_out, new_err From 5a42d0a14755448eaccd355e0b61b9485ec66331 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 20:50:05 +0200 Subject: [PATCH 1034/1650] Remove use of six.iteritems --- bpython/autocomplete.py | 3 +-- bpython/config.py | 12 ++++++------ bpython/curtsiesfrontend/interpreter.py | 4 ++-- bpython/curtsiesfrontend/manual_readline.py | 7 +++---- bpython/formatter.py | 3 +-- bpython/urwid.py | 3 +-- 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 0496fb3b2..e810386c7 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -30,7 +30,6 @@ import os import re import rlcompleter -from six import iteritems import builtins from . import inspection @@ -430,7 +429,7 @@ def matches(self, cursor_offset, line, **kwargs): if self.method_match(word, n, r.word): matches.add(word) for nspace in (builtins.__dict__, locals_): - for word, val in iteritems(nspace): + for word, val in nspace.items(): # if identifier isn't ascii, don't complete (syntax error) if word is None: continue diff --git a/bpython/config.py b/bpython/config.py index 345a639cb..319f8af7a 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -2,7 +2,7 @@ import sys import locale from itertools import chain -from six import iterkeys, iteritems +from six import iterkeys from configparser import ConfigParser from .autocomplete import SIMPLE as default_completion, ALL_MODES @@ -47,7 +47,7 @@ def fill_config_with_default_values(config, default_values): if not config.has_section(section): config.add_section(section) - for (opt, val) in iteritems(default_values[section]): + for (opt, val) in default_values[section].items(): if not config.has_option(section, opt): config.set(section, opt, "%s" % (val,)) @@ -122,9 +122,9 @@ def loadini(struct, configfile): "curtsies": {"list_above": False, "right_arrow_completion": True,}, } - default_keys_to_commands = dict( - (value, key) for (key, value) in iteritems(defaults["keyboard"]) - ) + default_keys_to_commands = { + value: key for (key, value) in defaults["keyboard"].items() + } fill_config_with_default_values(config, defaults) try: @@ -314,6 +314,6 @@ def load_theme(struct, path, colors, default_colors): colors[k] = theme.get("interface", k) # Check against default theme to see if all values are defined - for k, v in iteritems(default_colors): + for k, v in default_colors.items(): if k not in colors: colors[k] = v diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 702b21387..85ce95f4c 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,5 +1,5 @@ import sys -from six import iteritems, text_type +from six import text_type from pygments.token import Generic, Token, Keyword, Name, Comment, String from pygments.token import Error, Literal, Number, Operator, Punctuation @@ -45,7 +45,7 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} - for k, v in iteritems(color_scheme): + for k, v in color_scheme.items(): self.f_strings[k] = "\x01%s" % (v,) super().__init__(**options) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 621fc6740..31525cf39 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -7,7 +7,6 @@ from bpython.lazyre import LazyReCompile import inspect -from six import iteritems INDENT = 4 @@ -38,9 +37,9 @@ def add(self, key, func, overwrite=False): else: raise ValueError("key %r already has a mapping" % (key,)) params = getargspec(func) - args = dict( - (k, v) for k, v in iteritems(self.default_kwargs) if k in params - ) + args = { + k: v for k, v in self.default_kwargs.items() if k in params + } r = func(**args) if len(r) == 2: if hasattr(func, "kills"): diff --git a/bpython/formatter.py b/bpython/formatter.py index 872915b46..bd94e3afd 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -39,7 +39,6 @@ Literal, Punctuation, ) -from six import iteritems """These format strings are pretty ugly. \x01 represents a colour marker, which @@ -99,7 +98,7 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} - for k, v in iteritems(theme_map): + for k, v in theme_map.items(): self.f_strings[k] = "\x01%s" % (color_scheme[v],) if k is Parenthesis: # FIXME: Find a way to make this the inverse of the current diff --git a/bpython/urwid.py b/bpython/urwid.py index 116ef2b8f..a3acd9809 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -38,7 +38,6 @@ import locale import signal from optparse import Option -from six import iteritems from pygments.token import Token @@ -1188,7 +1187,7 @@ def main(args=None, locals_=None, banner=None): "default", "bold" if color.isupper() else "default", ) - for name, color in iteritems(config.color_scheme) + for name, color in config.color_scheme.items() ] palette.extend( [ From c2e7862deb3ade39f1d484723d87fec3063adcd1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 20:55:20 +0200 Subject: [PATCH 1035/1650] Remove use of six.iterkeys --- bpython/config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 319f8af7a..1c1181b21 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -2,7 +2,6 @@ import sys import locale from itertools import chain -from six import iterkeys from configparser import ConfigParser from .autocomplete import SIMPLE as default_completion, ALL_MODES @@ -43,7 +42,7 @@ def default_config_path(): def fill_config_with_default_values(config, default_values): - for section in iterkeys(default_values): + for section in default_values.keys(): if not config.has_section(section): config.add_section(section) From 50d443bc7738eb41f8d699b953b6509437ddce71 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 20:55:51 +0200 Subject: [PATCH 1036/1650] Remove use of six.itervalues --- bpython/repl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index e29a0d8cf..60da9669b 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -36,7 +36,6 @@ import time import traceback from itertools import takewhile -from six import itervalues from types import ModuleType from pygments.token import Token @@ -1075,7 +1074,7 @@ def tokenize(self, s, newline=False): stack.append( (line, len(line_tokens) - 1, line_tokens, value) ) - elif value in itervalues(parens): + elif value in parens.values(): saved_stack = list(stack) try: while True: From 6445871b74f7cb8d200a3bbc2f6ff170d38bef01 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 20:56:46 +0200 Subject: [PATCH 1037/1650] Remove use of six.text_type --- bpython/curtsiesfrontend/interpreter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 85ce95f4c..8ffdc99ec 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,5 +1,4 @@ import sys -from six import text_type from pygments.token import Generic, Token, Keyword, Name, Comment, String from pygments.token import Error, Literal, Number, Operator, Punctuation @@ -74,7 +73,7 @@ def write(err_line): """Default stderr handler for tracebacks Accepts FmtStrs so interpreters can output them""" - sys.stderr.write(text_type(err_line)) + sys.stderr.write(str(err_line)) self.write = write self.outfile = self From fe221535a49ec4c065234e53f2896d989783e424 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 20:57:45 +0200 Subject: [PATCH 1038/1650] Remove six from dependencies --- README.rst | 1 - requirements.txt | 1 - setup.py | 1 - 3 files changed, 3 deletions(-) diff --git a/README.rst b/README.rst index 9f3372a4f..8e13045af 100644 --- a/README.rst +++ b/README.rst @@ -99,7 +99,6 @@ Dependencies * requests * curtsies >= 0.3.0 * greenlet -* six >= 1.5 * Sphinx != 1.1.2 (optional, for the documentation) * mock (optional, for the testsuite) * babel (optional, for internationalization) diff --git a/requirements.txt b/requirements.txt index 25eb37004..397da7e93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,3 @@ curtsies >=0.3.0 greenlet requests setuptools -six >=1.5 diff --git a/setup.py b/setup.py index 3730b1692..ac9896fc4 100755 --- a/setup.py +++ b/setup.py @@ -223,7 +223,6 @@ def initialize_options(self): "requests", "curtsies >=0.3.0", "greenlet", - "six >=1.5", "wcwidth", ] From d3248ca029efcbeb241214b1c96830095ebf9322 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 21:06:06 +0200 Subject: [PATCH 1039/1650] Replace io.open with open --- bpython/args.py | 2 +- bpython/config.py | 2 +- bpython/curtsies.py | 3 +-- bpython/history.py | 7 +++---- bpython/inspection.py | 3 +-- bpython/repl.py | 3 +-- bpython/test/test_curtsies_repl.py | 2 +- bpython/test/test_history.py | 3 +-- bpython/urwid.py | 2 +- 9 files changed, 11 insertions(+), 16 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 6f47571a0..5f331ff6b 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -134,7 +134,7 @@ def exec_code(interpreter, args): Helper to execute code in a given interpreter. args should be a [faked] sys.argv """ - with open(args[0], "r") as sourcefile: + with open(args[0]) as sourcefile: source = sourcefile.read() old_argv, sys.argv = sys.argv, args sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) diff --git a/bpython/config.py b/bpython/config.py index 1c1181b21..1fe7761ed 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -304,7 +304,7 @@ def get_key_no_doublebind(command): def load_theme(struct, path, colors, default_colors): theme = ConfigParser() - with open(path, "r") as f: + with open(path) as f: theme.readfp(f) for k, v in chain(theme.items("syntax"), theme.items("interface")): if theme.has_option("syntax", k): diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 9b47cf44d..763f0ae28 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -1,5 +1,4 @@ import collections -import io import logging import sys from optparse import Option @@ -178,7 +177,7 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): if options.paste: paste = curtsies.events.PasteEvent() encoding = inspection.get_encoding_file(exec_args[0]) - with io.open(exec_args[0], encoding=encoding) as f: + with open(exec_args[0], encoding=encoding) as f: sourcecode = f.read() paste.events.extend(sourcecode) else: diff --git a/bpython/history.py b/bpython/history.py index 0fb29d4d4..d8c0ef784 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -21,7 +21,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import io import os import stat from itertools import islice @@ -170,7 +169,7 @@ def reset(self): self.saved_line = "" def load(self, filename, encoding): - with io.open( + with open( filename, "r", encoding=encoding, errors="ignore" ) as hfile: with FileLock(hfile, filename=filename): @@ -188,7 +187,7 @@ def save(self, filename, encoding, lines=0): os.O_WRONLY | os.O_CREAT | os.O_TRUNC, stat.S_IRUSR | stat.S_IWUSR, ) - with io.open(fd, "w", encoding=encoding, errors="ignore") as hfile: + with open(fd, "w", encoding=encoding, errors="ignore") as hfile: with FileLock(hfile, filename=filename): self.save_to(hfile, self.entries, lines) @@ -209,7 +208,7 @@ def append_reload_and_write(self, s, filename, encoding): os.O_APPEND | os.O_RDWR | os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR, ) - with io.open(fd, "a+", encoding=encoding, errors="ignore") as hfile: + with open(fd, "a+", encoding=encoding, errors="ignore") as hfile: with FileLock(hfile, filename=filename): # read entries hfile.seek(0, os.SEEK_SET) diff --git a/bpython/inspection.py b/bpython/inspection.py index 918d26ce2..f0495d5e7 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -23,7 +23,6 @@ import inspect -import io import keyword import pydoc from collections import namedtuple @@ -350,7 +349,7 @@ def get_encoding_comment(source): def get_encoding_file(fname): """Try to obtain encoding information from a Python source file.""" - with io.open(fname, "rt", encoding="ascii", errors="ignore") as f: + with open(fname, "rt", encoding="ascii", errors="ignore") as f: for unused in range(2): line = f.readline() match = get_encoding_line_re.search(line) diff --git a/bpython/repl.py b/bpython/repl.py index 60da9669b..d9a50349a 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -24,7 +24,6 @@ import code import inspect -import io import os import pkgutil import pydoc @@ -481,7 +480,7 @@ def startup(self): filename = os.environ.get("PYTHONSTARTUP") if filename: encoding = inspection.get_encoding_file(filename) - with io.open(filename, "rt", encoding=encoding) as f: + with open(filename, "rt", encoding=encoding) as f: source = f.read() self.interp.runsource(source, filename, "exec", encode=False) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index fb6e9aa09..f1fe17d0c 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -434,7 +434,7 @@ def setUp(self): self.repl = create_repl() def write_startup_file(self, fname, encoding): - with io.open(fname, mode="wt", encoding=encoding) as f: + with open(fname, mode="wt", encoding=encoding) as f: f.write("# coding: ") f.write(encoding) f.write("\n") diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index f8e931ada..6e8756a5c 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -1,4 +1,3 @@ -import io import os @@ -89,7 +88,7 @@ def setUp(self): self.filename = "history_temp_file" self.encoding = getpreferredencoding() - with io.open( + with open( self.filename, "w", encoding=self.encoding, errors="ignore" ) as f: f.write(b"#1\n#2\n".decode()) diff --git a/bpython/urwid.py b/bpython/urwid.py index a3acd9809..b79ecb5e5 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1338,7 +1338,7 @@ def start(main_loop, user_data): # this is CLIRepl.startup inlined. filename = os.environ.get("PYTHONSTARTUP") if filename and os.path.isfile(filename): - with open(filename, "r") as f: + with open(filename) as f: interpreter.runsource(f.read(), filename, "exec") if banner is not None: From eaef6d0d2c9cb92d2760e209f0d7c3a36a4bd7dc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 21:11:03 +0200 Subject: [PATCH 1040/1650] Use set literals --- bpython/autocomplete.py | 22 ++++++++--------- bpython/importcompletion.py | 2 +- bpython/test/test_autocomplete.py | 34 +++++++++++++-------------- bpython/test/test_importcompletion.py | 10 ++++---- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index e810386c7..fb0ffbc41 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -297,16 +297,16 @@ def matches(self, cursor_offset, line, **kwargs): i -= 1 break methodtext = r.word[-i:] - matches = set( + matches = { "".join([r.word[:-i], m]) for m in self.attr_matches(methodtext, locals_) - ) + } - return set( + return { m for m in matches if few_enough_underscores(r.word.split(".")[-1], m.split(".")[-1]) - ) + } def locate(self, current_offset, line): return lineparts.current_dotted_attribute(current_offset, line) @@ -376,11 +376,11 @@ def matches(self, cursor_offset, line, **kwargs): except EvaluationError: return None if isinstance(obj, dict) and obj.keys(): - matches = set( + matches = { "{0!r}]".format(k) for k in obj.keys() if repr(k).startswith(r.word) - ) + } return matches if matches else None else: return None @@ -403,7 +403,7 @@ def matches(self, cursor_offset, line, **kwargs): return None if "class" not in current_block: return None - return set(name for name in MAGIC_METHODS if name.startswith(r.word)) + return {name for name in MAGIC_METHODS if name.startswith(r.word)} def locate(self, current_offset, line): return lineparts.current_method_definition_name(current_offset, line) @@ -456,11 +456,11 @@ def matches(self, cursor_offset, line, **kwargs): if r is None: return None if argspec: - matches = set( + matches = { name + "=" for name in argspec[1][0] if isinstance(name, str) and name.startswith(r.word) - ) + } matches.update( name + "=" for name in argspec[1][4] @@ -494,7 +494,7 @@ def matches(self, cursor_offset, line, **kwargs): # strips leading dot matches = [m[1:] for m in self.attr_lookup(obj, "", attr.word)] - return set(m for m in matches if few_enough_underscores(attr.word, m)) + return {m for m in matches if few_enough_underscores(attr.word, m)} try: @@ -547,7 +547,7 @@ def matches(self, cursor_offset, line, **kwargs): return None else: # case-sensitive matches only - return set(m for m in matches if m.startswith(first_letter)) + return {m for m in matches if m.startswith(first_letter)} def locate(self, cursor_offset, line): start = self._orig_start diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 97e02223d..399ffd3b9 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -56,7 +56,7 @@ def module_matches(cw, prefix=""): if (name.startswith(full) and name.find(".", len(full)) == -1) ) if prefix: - return set(match[len(prefix) + 1 :] for match in matches) + return {match[len(prefix) + 1 :] for match in matches} else: return set(matches) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index a59c8a3a6..65beb0118 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -110,7 +110,7 @@ def test_two_completers_get_both(self): a = self.completer(["a"]) b = self.completer(["b"]) cumulative = autocomplete.CumulativeCompleter([a, b]) - self.assertEqual(cumulative.matches(3, "abc"), set(["a", "b"])) + self.assertEqual(cumulative.matches(3, "abc"), {"a", "b"}) class TestFilenameCompletion(unittest.TestCase): @@ -187,7 +187,7 @@ def test_set_of_keys_returned_when_matches_found(self): com = autocomplete.DictKeyCompletion() local = {"d": {"ab": 1, "cd": 2}} self.assertSetEqual( - com.matches(2, "d[", locals_=local), set(["'ab']", "'cd']"]) + com.matches(2, "d[", locals_=local), {"'ab']", "'cd']"} ) def test_none_returned_when_eval_error(self): @@ -254,27 +254,27 @@ def setUpClass(cls): def test_att_matches_found_on_instance(self): self.assertSetEqual( self.com.matches(2, "a.", locals_={"a": Foo()}), - set(["a.method", "a.a", "a.b"]), + {"a.method", "a.a", "a.b"}, ) def test_descriptor_attributes_not_run(self): com = autocomplete.AttrCompletion() self.assertSetEqual( com.matches(2, "a.", locals_={"a": Properties()}), - set(["a.b", "a.a", "a.method", "a.asserts_when_called"]), + {"a.b", "a.a", "a.method", "a.asserts_when_called"}, ) def test_custom_get_attribute_not_invoked(self): com = autocomplete.AttrCompletion() self.assertSetEqual( com.matches(2, "a.", locals_={"a": OverriddenGetattribute()}), - set(["a.b", "a.a", "a.method"]), + {"a.b", "a.a", "a.method"}, ) def test_slots_not_crash(self): com = autocomplete.AttrCompletion() self.assertSetEqual( - com.matches(2, "A.", locals_={"A": Slots}), set(["A.b", "A.a"]), + com.matches(2, "A.", locals_={"A": Slots}), {"A.b", "A.a"}, ) @@ -286,7 +286,7 @@ def setUpClass(cls): def test_att_matches_found_on_instance(self): self.assertSetEqual( self.com.matches(5, "a[0].", locals_={"a": [Foo()]}), - set(["method", "a", "b"]), + {"method", "a", "b"}, ) def test_other_getitem_methods_not_called(self): @@ -299,7 +299,7 @@ def __getitem__(inner_self, i): def test_tuples_complete(self): self.assertSetEqual( self.com.matches(5, "a[0].", locals_={"a": (Foo(),)}), - set(["method", "a", "b"]), + {"method", "a", "b"}, ) @unittest.skip("TODO, subclasses do not complete yet") @@ -309,7 +309,7 @@ class ListSubclass(list): self.assertSetEqual( self.com.matches(5, "a[0].", locals_={"a": ListSubclass([Foo()])}), - set(["method", "a", "b"]), + {"method", "a", "b"}, ) def test_getitem_not_called_in_list_subclasses_overriding_getitem(self): @@ -322,13 +322,13 @@ def __getitem__(inner_self, i): def test_literals_complete(self): self.assertSetEqual( self.com.matches(10, "[a][0][0].", locals_={"a": (Foo(),)}), - set(["method", "a", "b"]), + {"method", "a", "b"}, ) def test_dictionaries_complete(self): self.assertSetEqual( self.com.matches(7, 'a["b"].', locals_={"a": {"b": Foo()}}), - set(["method", "a", "b"]), + {"method", "a", "b"}, ) @@ -391,7 +391,7 @@ def test_completions_starting_with_different_cases(self): ["adsf"], [Comp("Abc", "bc"), Comp("ade", "de")], ) - self.assertSetEqual(matches, set(["ade"])) + self.assertSetEqual(matches, {"ade"}) def test_issue_544(self): com = autocomplete.MultilineJediCompletion() @@ -410,12 +410,12 @@ def function(): self.assertEqual( self.com.matches(8, "function", locals_={"function": function}), - set(("function(",)), + {"function("}, ) def test_completions_are_unicode(self): for m in self.com.matches(1, "a", locals_={"abc": 10}): - self.assertIsInstance(m, type(u"")) + self.assertIsInstance(m, type("")) def test_mock_kwlist(self): with mock.patch.object(keyword, "kwlist", new=["abcd"]): @@ -435,11 +435,11 @@ def func(apple, apricot, banana, carrot): argspec = ["func", argspec, False] com = autocomplete.ParameterNameCompletion() self.assertSetEqual( - com.matches(1, "a", argspec=argspec), set(["apple=", "apricot="]) + com.matches(1, "a", argspec=argspec), {"apple=", "apricot="} ) self.assertSetEqual( - com.matches(2, "ba", argspec=argspec), set(["banana="]) + com.matches(2, "ba", argspec=argspec), {"banana="} ) self.assertSetEqual( - com.matches(3, "car", argspec=argspec), set(["carrot="]) + com.matches(3, "car", argspec=argspec), {"carrot="} ) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 4b018f904..af710b2ef 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -18,13 +18,13 @@ def tearDown(self): def test_simple_completion(self): self.assertSetEqual( - importcompletion.complete(10, "import zza"), set(["zzabc", "zzabd"]) + importcompletion.complete(10, "import zza"), {"zzabc", "zzabd"} ) def test_package_completion(self): self.assertSetEqual( importcompletion.complete(13, "import zzabc."), - set(["zzabc.e", "zzabc.f"]), + {"zzabc.e", "zzabc.f"}, ) @@ -43,15 +43,15 @@ def tearDownClass(cls): def test_from_attribute(self): self.assertSetEqual( - importcompletion.complete(19, "from sys import arg"), set(["argv"]) + importcompletion.complete(19, "from sys import arg"), {"argv"} ) def test_from_attr_module(self): self.assertSetEqual( - importcompletion.complete(9, "from os.p"), set(["os.path"]) + importcompletion.complete(9, "from os.p"), {"os.path"} ) def test_from_package(self): self.assertSetEqual( - importcompletion.complete(17, "from xml import d"), set(["dom"]) + importcompletion.complete(17, "from xml import d"), {"dom"} ) From ebbef7bacbb53908fa0ff86e4cdcd325b2f8057c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 21:21:55 +0200 Subject: [PATCH 1041/1650] Use dict literals --- bpython/curtsiesfrontend/manual_readline.py | 2 +- bpython/simpleeval.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 31525cf39..4a2b95cc3 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -70,7 +70,7 @@ def add_config_attr(self, config_attr, func): def call(self, key, **kwargs): func = self[key] params = getargspec(func) - args = dict((k, v) for k, v in kwargs.items() if k in params) + args = {k: v for k, v in kwargs.items() if k in params} return func(**args) def call_without_cut(self, key, **kwargs): diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 7fa2a2f00..8ab902903 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -98,10 +98,10 @@ def _convert(node): elif isinstance(node, ast.List): return list(map(_convert, node.elts)) elif isinstance(node, ast.Dict): - return dict( - (_convert(k), _convert(v)) + return { + _convert(k): _convert(v) for k, v in zip(node.keys, node.values) - ) + } elif isinstance(node, ast.Set): return set(map(_convert, node.elts)) elif ( From ea9ac3319e1db03bed1ec59192f92e9115b542d8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 21:23:52 +0200 Subject: [PATCH 1042/1650] Replace unicode literals --- bpython/inspection.py | 6 +++--- bpython/repl.py | 10 +++++----- bpython/test/test_curtsies_coderunner.py | 2 +- bpython/test/test_inspection.py | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index f0495d5e7..aa1902c2e 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -121,14 +121,14 @@ def parsekeywordpairs(signature): parendepth = 0 for token, value in tokens: if preamble: - if token is Token.Punctuation and value == u"(": + if token is Token.Punctuation and value == "(": preamble = False continue if token is Token.Punctuation: - if value in [u"(", u"{", u"["]: + if value in ["(", "{", "["]: parendepth += 1 - elif value in [u")", u"}", u"]"]: + elif value in [")", "}", "]"]: parendepth -= 1 elif value == ":" and parendepth == -1: # End of signature reached diff --git a/bpython/repl.py b/bpython/repl.py index d9a50349a..84d158271 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -470,7 +470,7 @@ def ps2(self): try: return sys.ps2 except AttributeError: - return u"... " + return "... " def startup(self): """ @@ -659,11 +659,11 @@ def get_source_of_current_name(self): obj = self.get_object(line) return inspection.get_source_unicode(obj) except (AttributeError, NameError) as e: - msg = _(u"Cannot get source: %s") % (e,) + msg = _("Cannot get source: %s") % (e,) except IOError as e: - msg = u"%s" % (e,) + msg = f"{e}" except TypeError as e: - if "built-in" in u"%s" % (e,): + if "built-in" in f"{e}": msg = _("Cannot access source of %r") % (obj,) else: msg = _("No source code found for %s") % (self.current_line,) @@ -946,7 +946,7 @@ def insert_into_history(self, s): s, pythonhist, getpreferredencoding() ) except RuntimeError as e: - self.interact.notify(u"%s" % (e,)) + self.interact.notify(f"{e}") def prompt_undo(self): """Returns how many lines to undo, 0 means don't undo""" diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index 1cdb33669..ec0dbfe0b 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -46,7 +46,7 @@ def ctrlc(): class TestFakeOutput(unittest.TestCase): def assert_unicode(self, s): - self.assertIsInstance(s, type(u"")) + self.assertIsInstance(s, type("")) def test_bytes(self): out = FakeOutput(mock.Mock(), self.assert_unicode, None) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index c41361478..9e0892437 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -7,12 +7,12 @@ from bpython.test.fodder import encoding_utf8 -foo_ascii_only = u'''def foo(): +foo_ascii_only = '''def foo(): """Test""" pass ''' -foo_non_ascii = u'''def foo(): +foo_non_ascii = '''def foo(): """Test äöü""" pass ''' From 3b83c0206611c7f0b49e66045ec725e81f6f2787 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 21:25:42 +0200 Subject: [PATCH 1043/1650] Use str.format and f-strings where possible --- bpython/args.py | 2 +- bpython/autocomplete.py | 6 +++--- bpython/cli.py | 16 ++++++++-------- bpython/config.py | 4 ++-- bpython/curtsiesfrontend/filewatch.py | 4 ++-- bpython/curtsiesfrontend/interpreter.py | 4 ++-- bpython/curtsiesfrontend/manual_readline.py | 10 +++++----- bpython/curtsiesfrontend/repl.py | 8 ++++---- bpython/curtsiesfrontend/replpainter.py | 4 ++-- bpython/formatter.py | 4 ++-- bpython/importcompletion.py | 10 +++++----- bpython/repl.py | 2 +- bpython/simpleeval.py | 8 ++++---- bpython/simplerepl.py | 2 +- bpython/test/__init__.py | 2 +- bpython/test/test_curtsies_painting.py | 2 +- bpython/test/test_curtsies_repl.py | 2 +- bpython/test/test_preprocess.py | 6 +++--- bpython/urwid.py | 6 +++--- setup.py | 2 +- 20 files changed, 52 insertions(+), 52 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 5f331ff6b..578e46836 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -24,7 +24,7 @@ def error(self, msg): def version_banner(): - return "bpython version %s on top of Python %s %s" % ( + return "bpython version {} on top of Python {} {}".format( __version__, sys.version.split()[0], sys.executable, diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index fb0ffbc41..4e062481a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -351,7 +351,7 @@ def attr_lookup(self, obj, expr, attr): n = len(attr) for word in words: if self.method_match(word, n, attr) and word != "__builtins__": - matches.append("%s.%s" % (expr, word)) + matches.append(f"{expr}.{word}") return matches def list_attributes(self, obj): @@ -377,7 +377,7 @@ def matches(self, cursor_offset, line, **kwargs): return None if isinstance(obj, dict) and obj.keys(): matches = { - "{0!r}]".format(k) + f"{k!r}]" for k in obj.keys() if repr(k).startswith(r.word) } @@ -562,7 +562,7 @@ def matches(self, cursor_offset, line, **kwargs): history = kwargs["history"] if "\n" in current_block: - assert cursor_offset <= len(line), "%r %r" % ( + assert cursor_offset <= len(line), "{!r} {!r}".format( cursor_offset, line, ) diff --git a/bpython/cli.py b/bpython/cli.py index 1f1a99e08..4e64e3569 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -773,7 +773,7 @@ def mkargspec(self, topline, in_arg, down): if args: self.list_win.addstr(", ", punctuation_colpair) self.list_win.addstr( - "*%s" % (_args,), get_colpair(self.config, "token") + f"*{_args}", get_colpair(self.config, "token") ) if kwonly: @@ -799,7 +799,7 @@ def mkargspec(self, topline, in_arg, down): if args or _args or kwonly: self.list_win.addstr(", ", punctuation_colpair) self.list_win.addstr( - "**%s" % (_kwargs,), get_colpair(self.config, "token") + f"**{_kwargs}", get_colpair(self.config, "token") ) self.list_win.addstr(")", punctuation_colpair) @@ -978,7 +978,7 @@ def p_key(self, key): try: source = self.get_source_of_current_name() except repl.SourceNotFound as e: - self.statusbar.message("%s" % (e,)) + self.statusbar.message(f"{e}") else: if config.highlight_show_source: source = format( @@ -1072,7 +1072,7 @@ def prompt(self, more): """Show the appropriate Python prompt""" if not more: self.echo( - "\x01%s\x03%s" % (self.config.color_scheme["prompt"], self.ps1) + "\x01{}\x03{}".format(self.config.color_scheme["prompt"], self.ps1) ) self.stdout_hist += self.ps1 self.screen_hist.append( @@ -1081,10 +1081,10 @@ def prompt(self, more): ) else: prompt_more_color = self.config.color_scheme["prompt_more"] - self.echo("\x01%s\x03%s" % (prompt_more_color, self.ps2)) + self.echo(f"\x01{prompt_more_color}\x03{self.ps2}") self.stdout_hist += self.ps2 self.screen_hist.append( - "\x01%s\x03%s\x04" % (prompt_more_color, self.ps2) + f"\x01{prompt_more_color}\x03{self.ps2}\x04" ) def push(self, s, insert_into_history=True): @@ -1480,7 +1480,7 @@ def undo(self, n=1): def writetb(self, lines): for line in lines: self.write( - "\x01%s\x03%s" % (self.config.color_scheme["error"], line) + "\x01{}\x03{}".format(self.config.color_scheme["error"], line) ) def yank_from_buffer(self): @@ -1700,7 +1700,7 @@ def init_wins(scr, config): ) message = " ".join( - "<%s> %s" % (key, command) for command, key in commands if key + f"<{key}> {command}" for command, key in commands if key ) statusbar = Statusbar( diff --git a/bpython/config.py b/bpython/config.py index 1fe7761ed..7a429b848 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -48,7 +48,7 @@ def fill_config_with_default_values(config, default_values): for (opt, val) in default_values[section].items(): if not config.has_option(section, opt): - config.set(section, opt, "%s" % (val,)) + config.set(section, opt, f"{val}") def loadini(struct, configfile): @@ -270,7 +270,7 @@ def get_key_no_doublebind(command): load_theme(struct, path, struct.color_scheme, default_colors) except EnvironmentError: sys.stderr.write( - "Could not load theme '%s'.\n" % (color_scheme_name,) + f"Could not load theme '{color_scheme_name}'.\n" ) sys.exit(1) diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 61435271c..676736737 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -58,7 +58,7 @@ def track_module(self, path): def activate(self): if self.activated: - raise ValueError("%r is already activated." % (self,)) + raise ValueError(f"{self!r} is already activated.") if not self.started: self.started = True self.observer.start() @@ -71,7 +71,7 @@ def activate(self): def deactivate(self): if not self.activated: - raise ValueError("%r is not activated." % (self,)) + raise ValueError(f"{self!r} is not activated.") self.observer.unschedule_all() self.activated = False diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 8ffdc99ec..6d62c65a7 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -45,7 +45,7 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} for k, v in color_scheme.items(): - self.f_strings[k] = "\x01%s" % (v,) + self.f_strings[k] = f"\x01{v}" super().__init__(**options) def format(self, tokensource, outfile): @@ -54,7 +54,7 @@ def format(self, tokensource, outfile): for token, text in tokensource: while token not in self.f_strings: token = token.parent - o += "%s\x03%s\x04" % (self.f_strings[token], text) + o += "{}\x03{}\x04".format(self.f_strings[token], text) outfile.write(parse(o.rstrip())) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 4a2b95cc3..a77107e10 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -35,7 +35,7 @@ def add(self, key, func, overwrite=False): if overwrite: del self[key] else: - raise ValueError("key %r already has a mapping" % (key,)) + raise ValueError(f"key {key!r} already has a mapping") params = getargspec(func) args = { k: v for k, v in self.default_kwargs.items() if k in params @@ -57,13 +57,13 @@ def add(self, key, func, overwrite=False): self.cut_buffer_edits[key] = func else: raise ValueError( - "return type of function %r not recognized" % (func,) + f"return type of function {func!r} not recognized" ) def add_config_attr(self, config_attr, func): if config_attr in self.awaiting_config: raise ValueError( - "config attribute %r already has a mapping" % (config_attr,) + f"config attribute {config_attr!r} already has a mapping" ) self.awaiting_config[config_attr] = func @@ -84,7 +84,7 @@ def __getitem__(self, key): return self.simple_edits[key] if key in self.cut_buffer_edits: return self.cut_buffer_edits[key] - raise KeyError("key %r not mapped" % (key,)) + raise KeyError(f"key {key!r} not mapped") def __delitem__(self, key): if key in self.simple_edits: @@ -92,7 +92,7 @@ def __delitem__(self, key): elif key in self.cut_buffer_edits: del self.cut_buffer_edits[key] else: - raise KeyError("key %r not mapped" % (key,)) + raise KeyError(f"key {key!r} not mapped") class UnconfiguredEdits(AbstractEdits): diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d93b6c4cb..dfb0572d8 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1350,13 +1350,13 @@ def display_line_with_prompt(self): if self.incr_search_mode == "reverse_incremental_search": return ( prompt( - "(reverse-i-search)`{}': ".format(self.incr_search_target) + f"(reverse-i-search)`{self.incr_search_target}': " ) + self.current_line_formatted ) elif self.incr_search_mode == "incremental_search": return ( - prompt("(i-search)`%s': ".format(self.incr_search_target)) + prompt(f"(i-search)`%s': ") + self.current_line_formatted ) return ( @@ -1976,7 +1976,7 @@ def show_source(self): try: source = self.get_source_of_current_name() except SourceNotFound as e: - self.status_bar.message("%s" % (e,)) + self.status_bar.message(f"{e}") else: if self.config.highlight_show_source: source = pygformat( @@ -2028,7 +2028,7 @@ def key_help_text(self): max_func = max(len(func) for func, key in pairs) return "\n".join( - "%s : %s" % (func.rjust(max_func), key) for func, key in pairs + "{} : {}".format(func.rjust(max_func), key) for func, key in pairs ) diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 481ad67dd..d7de5fd0e 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -133,7 +133,7 @@ def formatted_argspec(funcprops, arg_pos, columns, config): if _args: if args: s += punctuation_color(", ") - s += token_color("*%s" % (_args,)) + s += token_color(f"*{_args}") if kwonly: if not _args: @@ -155,7 +155,7 @@ def formatted_argspec(funcprops, arg_pos, columns, config): if _kwargs: if args or _args or kwonly: s += punctuation_color(", ") - s += token_color("**%s" % (_kwargs,)) + s += token_color(f"**{_kwargs}") s += punctuation_color(")") return linesplit(s, columns) diff --git a/bpython/formatter.py b/bpython/formatter.py index bd94e3afd..9fdd43a57 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -99,7 +99,7 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} for k, v in theme_map.items(): - self.f_strings[k] = "\x01%s" % (color_scheme[v],) + self.f_strings[k] = "\x01{}".format(color_scheme[v]) if k is Parenthesis: # FIXME: Find a way to make this the inverse of the current # background colour @@ -114,7 +114,7 @@ def format(self, tokensource, outfile): while token not in self.f_strings: token = token.parent - o += "%s\x03%s\x04" % (self.f_strings[token], text) + o += "{}\x03{}\x04".format(self.f_strings[token], text) outfile.write(o.rstrip()) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 399ffd3b9..1176909f4 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -49,7 +49,7 @@ def module_matches(cw, prefix=""): """Modules names to replace cw with""" - full = "%s.%s" % (prefix, cw) if prefix else cw + full = f"{prefix}.{cw}" if prefix else cw matches = ( name for name in modules @@ -63,7 +63,7 @@ def module_matches(cw, prefix=""): def attr_matches(cw, prefix="", only_modules=False): """Attributes to replace name with""" - full = "%s.%s" % (prefix, cw) if prefix else cw + full = f"{prefix}.{cw}" if prefix else cw module_name, _, name_after_dot = full.rpartition(".") if module_name not in sys.modules: return set() @@ -73,7 +73,7 @@ def attr_matches(cw, prefix="", only_modules=False): name for name in dir(module) if name.startswith(name_after_dot) - and "%s.%s" % (module_name, name) in sys.modules + and f"{module_name}.{name}" in sys.modules } else: matches = { @@ -81,7 +81,7 @@ def attr_matches(cw, prefix="", only_modules=False): } module_part, _, _ = cw.rpartition(".") if module_part: - matches = {"%s.%s" % (module_part, m) for m in matches} + matches = {f"{module_part}.{m}" for m in matches} return matches @@ -185,7 +185,7 @@ def find_modules(path): paths.add(path_real) for subname in find_modules(pathname): if subname != "__init__": - yield "%s.%s" % (name, subname) + yield f"{name}.{subname}" yield name diff --git a/bpython/repl.py b/bpython/repl.py index 84d158271..2800777ea 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -812,7 +812,7 @@ def process(): elif line.startswith(self.ps2): yield line[len(self.ps2) :] elif line.rstrip(): - yield "# OUT: %s" % (line,) + yield f"# OUT: {line}" return '\n'.join(process()) def write2file(self): diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 8ab902903..098f93ee1 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -171,7 +171,7 @@ def _convert(node): attr = node.attr return getattr_safe(obj, attr) - raise ValueError("malformed node or string: {!r}".format(node)) + raise ValueError(f"malformed node or string: {node!r}") return _convert(node_or_string) @@ -182,8 +182,8 @@ def safe_getitem(obj, index): try: return obj[index] except (KeyError, IndexError): - raise EvaluationError("can't lookup key %r on %r" % (index, obj)) - raise ValueError("unsafe to lookup on object of type %s" % (type(obj),)) + raise EvaluationError(f"can't lookup key {index!r} on {obj!r}") + raise ValueError("unsafe to lookup on object of type {}".format(type(obj))) def find_attribute_with_name(node, name): @@ -255,5 +255,5 @@ def evaluate_current_attribute(cursor_offset, line, namespace=None): return getattr(obj, attr.word) except AttributeError: raise EvaluationError( - "can't lookup attribute %s on %r" % (attr.word, obj) + f"can't lookup attribute {attr.word} on {obj!r}" ) diff --git a/bpython/simplerepl.py b/bpython/simplerepl.py index 85595d331..9ec6bea86 100644 --- a/bpython/simplerepl.py +++ b/bpython/simplerepl.py @@ -51,7 +51,7 @@ def _schedule_refresh(self, when="now"): self.request_refresh() else: dt = round(when - time.time(), 1) - self.out("please refresh in {} seconds".format(dt)) + self.out(f"please refresh in {dt} seconds") def _request_reload(self, files_modified=("?",)): e = bpythonevents.ReloadEvent() diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index d1e21380a..896f856fe 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -19,7 +19,7 @@ class MagicIterMock(mock.MagicMock): def builtin_target(obj): """Returns mock target string of a builtin""" - return "%s.%s" % (builtins.__name__, obj.__name__) + return f"{builtins.__name__}.{obj.__name__}" TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 47b1b5602..720b5966e 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -185,7 +185,7 @@ def test_nonsense_docstrings(self): docstring, 40, config=setup_config() ) except Exception: - self.fail("bad docstring caused crash: {!r}".format(docstring)) + self.fail(f"bad docstring caused crash: {docstring!r}") def test_weird_boto_docstrings(self): # Boto does something like this. diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index f1fe17d0c..b1eb38997 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -30,7 +30,7 @@ def setup_config(conf): config.loadini(config_struct, TEST_CONFIG) for key, value in conf.items(): if not hasattr(config_struct, key): - raise ValueError("%r is not a valid config attribute" % (key,)) + raise ValueError(f"{key!r} is not a valid config attribute") setattr(config_struct, key, value) return config_struct diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index 364c8ecd9..143dfd3e7 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -15,7 +15,7 @@ def get_fodder_source(test_name): - pattern = r"#StartTest-%s\n(.*?)#EndTest" % (test_name,) + pattern = fr"#StartTest-{test_name}\n(.*?)#EndTest" orig, xformed = [ re.search(pattern, inspect.getsource(module), re.DOTALL) for module in [original, processed] @@ -23,11 +23,11 @@ def get_fodder_source(test_name): if not orig: raise ValueError( - "Can't locate test %s in original fodder file" % (test_name,) + f"Can't locate test {test_name} in original fodder file" ) if not xformed: raise ValueError( - "Can't locate test %s in processed fodder file" % (test_name,) + f"Can't locate test {test_name} in processed fodder file" ) return orig.group(1), xformed.group(1) diff --git a/bpython/urwid.py b/bpython/urwid.py index b79ecb5e5..c22c8fb8a 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1172,7 +1172,7 @@ def main(args=None, locals_=None, banner=None): # Stolen from twisted.application.app (twistd). for r in reactors.getReactorTypes(): - print(" %-4s\t%s" % (r.shortName, r.description)) + print(f" {r.shortName:<4}\t{r.description}") except ImportError: sys.stderr.write( "No reactors are available. Please install " @@ -1214,7 +1214,7 @@ def main(args=None, locals_=None, banner=None): if reactor is None: from twisted.internet import reactor except reactors.NoSuchReactor: - sys.stderr.write("Reactor %s does not exist\n" % (options.reactor,)) + sys.stderr.write(f"Reactor {options.reactor} does not exist\n") return event_loop = TwistedEventLoop(reactor) elif options.twisted: @@ -1249,7 +1249,7 @@ def main(args=None, locals_=None, banner=None): if plug.tapname == options.plugin: break else: - sys.stderr.write("Plugin %s does not exist\n" % (options.plugin,)) + sys.stderr.write(f"Plugin {options.plugin} does not exist\n") return plugopts = plug.options() plugopts.parseOptions(exec_args) diff --git a/setup.py b/setup.py index ac9896fc4..432c13b0b 100755 --- a/setup.py +++ b/setup.py @@ -107,7 +107,7 @@ def git_describe_to_python_version(version): with open(version_file, "w") as vf: vf.write("# Auto-generated file, do not edit!\n") - vf.write("__version__ = '%s'\n" % (version,)) + vf.write(f"__version__ = '{version}'\n") class install(_install): From 3cfda64c1e4f32649e8e726a2267aa5040e2bc78 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 21:28:16 +0200 Subject: [PATCH 1044/1650] Catch and raise OSError --- bpython/cli.py | 2 +- bpython/config.py | 2 +- bpython/curtsiesfrontend/repl.py | 4 ++-- bpython/filelock.py | 2 +- bpython/history.py | 2 +- bpython/importcompletion.py | 4 ++-- bpython/inspection.py | 2 +- bpython/pager.py | 2 +- bpython/repl.py | 8 ++++---- setup.py | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 4e64e3569..99a1b87f9 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -168,7 +168,7 @@ def flush(self): def write(self, value): # XXX IPython expects sys.stdin.write to exist, there will no doubt be # others, so here's a hack to keep them happy - raise IOError(errno.EBADF, "sys.stdin is read-only") + raise OSError(errno.EBADF, "sys.stdin is read-only") def isatty(self): return True diff --git a/bpython/config.py b/bpython/config.py index 7a429b848..ff7c6da3b 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -268,7 +268,7 @@ def get_key_no_doublebind(command): ) try: load_theme(struct, path, struct.color_scheme, default_colors) - except EnvironmentError: + except OSError: sys.stderr.write( f"Could not load theme '{color_scheme_name}'.\n" ) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index dfb0572d8..ebfb87ac6 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -198,7 +198,7 @@ def flush(self): def write(self, value): # XXX IPython expects sys.stdin.write to exist, there will no doubt be # others, so here's a hack to keep them happy - raise IOError(errno.EBADF, "sys.stdin is read-only") + raise OSError(errno.EBADF, "sys.stdin is read-only") def close(self): # hack to make closing stdin a nop @@ -666,7 +666,7 @@ def process_control_event(self, e): elif isinstance(e, bpythonevents.RunStartupFileEvent): try: self.startup() - except IOError as e: + except OSError as e: self.status_bar.message( _("Executing PYTHONSTARTUP failed: %s") % (e,) ) diff --git a/bpython/filelock.py b/bpython/filelock.py index 30bc0684d..e8217c95d 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -80,7 +80,7 @@ def acquire(self): try: fcntl.flock(self.fileobj, self.mode) self.locked = True - except IOError as e: + except OSError as e: if e.errno != errno.ENOLCK: raise e diff --git a/bpython/history.py b/bpython/history.py index d8c0ef784..0f1de12a6 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -221,7 +221,7 @@ def append_reload_and_write(self, s, filename, encoding): self.save_to(hfile, entries, self.hist_size) self.entries = entries - except EnvironmentError as err: + except OSError as err: raise RuntimeError( _("Error occurred while writing to file %s (%s)") % (filename, err.strerror) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 1176909f4..6e30861a2 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -136,7 +136,7 @@ def find_modules(path): try: filenames = os.listdir(path) - except EnvironmentError: + except OSError: filenames = [] finder = importlib.machinery.FileFinder(path) @@ -172,7 +172,7 @@ def find_modules(path): is_package = True else: pathname = spec.origin - except (ImportError, IOError, SyntaxError): + except (ImportError, OSError, SyntaxError): continue except UnicodeEncodeError: # Happens with Python 3 when there is a filename in some diff --git a/bpython/inspection.py b/bpython/inspection.py index aa1902c2e..0014c4f84 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -164,7 +164,7 @@ def fixlongargs(f, argspec): keys = argspec[0][-len(values) :] try: src = inspect.getsourcelines(f) - except (IOError, IndexError): + except (OSError, IndexError): # IndexError is raised in inspect.findsource(), can happen in # some situations. See issue #94. return diff --git a/bpython/pager.py b/bpython/pager.py index 98bfd0713..13f8c3234 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -58,7 +58,7 @@ def page(data, use_internal=False): # pager command not found, fall back to internal pager page_internal(data) return - except IOError as e: + except OSError as e: if e.errno != errno.EPIPE: raise while True: diff --git a/bpython/repl.py b/bpython/repl.py index 2800777ea..0d5e483a6 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -445,7 +445,7 @@ def __init__(self, interp, config): self.rl_history.load( pythonhist, getpreferredencoding() or "ascii" ) - except EnvironmentError: + except OSError: pass self.completers = autocomplete.get_default_completer( @@ -660,7 +660,7 @@ def get_source_of_current_name(self): return inspection.get_source_unicode(obj) except (AttributeError, NameError) as e: msg = _("Cannot get source: %s") % (e,) - except IOError as e: + except OSError as e: msg = f"{e}" except TypeError as e: if "built-in" in f"{e}": @@ -857,7 +857,7 @@ def write2file(self): try: with open(fn, mode) as f: f.write(stdout_text) - except IOError as e: + except OSError as e: self.interact.notify(_("Error writing file '%s': %s") % (fn, e)) else: self.interact.notify(_("Saved to %s.") % (fn,)) @@ -1166,7 +1166,7 @@ def edit_config(self): os.makedirs(containing_dir) with open(self.config.config_path, "w") as f: f.write(default_config) - except (IOError, OSError) as e: + except OSError as e: self.interact.notify( _("Error writing file '%s': %s") % (self.config.config.path, e) diff --git a/setup.py b/setup.py index 432c13b0b..9e55b0077 100755 --- a/setup.py +++ b/setup.py @@ -102,7 +102,7 @@ def git_describe_to_python_version(version): with open(version_file) as vf: version = vf.read().strip().split("=")[-1].replace("'", "") version = version.strip() - except IOError: + except OSError: pass with open(version_file, "w") as vf: From ad335eb6b412ac55135783f2ee98dbc133f64f56 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 21:54:46 +0200 Subject: [PATCH 1045/1650] Update translations --- bpython/translations/bpython.pot | 142 ++++++++--------- .../translations/de/LC_MESSAGES/bpython.po | 150 +++++++++--------- .../translations/es_ES/LC_MESSAGES/bpython.po | 140 ++++++++-------- .../translations/fr_FR/LC_MESSAGES/bpython.po | 140 ++++++++-------- .../translations/it_IT/LC_MESSAGES/bpython.po | 140 ++++++++-------- .../translations/nl_NL/LC_MESSAGES/bpython.po | 140 ++++++++-------- 6 files changed, 411 insertions(+), 441 deletions(-) diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 99f58d7e2..443101f32 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.19.dev37\n" +"Project-Id-Version: bpython 0.21.dev33\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-01-05 13:08+0000\n" +"POT-Creation-Date: 2020-10-13 21:53+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,306 +17,300 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:66 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:81 +#: bpython/args.py:78 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:87 +#: bpython/args.py:84 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:95 +#: bpython/args.py:92 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:101 +#: bpython/args.py:98 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "y" msgstr "" -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "yes" msgstr "" -#: bpython/cli.py:1743 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "" -#: bpython/cli.py:1744 +#: bpython/cli.py:1696 msgid "Save" msgstr "" -#: bpython/cli.py:1745 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1746 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1747 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "" -#: bpython/cli.py:1994 +#: bpython/cli.py:1946 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 -msgid "" -"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " -"been deprecated in version 0.19 and might disappear in a future version." -msgstr "" - -#: bpython/curtsies.py:152 +#: bpython/curtsies.py:147 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:158 +#: bpython/curtsies.py:153 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:231 +#: bpython/history.py:226 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:95 +#: bpython/paste.py:86 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:97 +#: bpython/paste.py:88 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:103 +#: bpython/paste.py:94 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:108 +#: bpython/paste.py:99 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:115 +#: bpython/paste.py:106 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:690 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:695 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:700 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:702 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:841 +#: bpython/repl.py:823 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 +#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:857 +#: bpython/repl.py:839 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:865 +#: bpython/repl.py:847 msgid "overwrite" msgstr "" -#: bpython/repl.py:867 +#: bpython/repl.py:849 msgid "append" msgstr "" -#: bpython/repl.py:879 bpython/repl.py:1192 +#: bpython/repl.py:861 bpython/repl.py:1171 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:881 +#: bpython/repl.py:863 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:887 +#: bpython/repl.py:869 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:876 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:896 +#: bpython/repl.py:878 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:905 +#: bpython/repl.py:887 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:907 +#: bpython/repl.py:889 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:915 +#: bpython/repl.py:897 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:921 +#: bpython/repl.py:903 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:925 +#: bpython/repl.py:907 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:934 +#: bpython/repl.py:916 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:939 +#: bpython/repl.py:921 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:977 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:985 bpython/repl.py:989 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:992 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1172 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1202 +#: bpython/repl.py:1181 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1208 +#: bpython/repl.py:1187 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:628 +#: bpython/urwid.py:618 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -#: bpython/urwid.py:1177 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1182 +#: bpython/urwid.py:1141 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1190 +#: bpython/urwid.py:1149 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1195 +#: bpython/urwid.py:1154 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1204 +#: bpython/urwid.py:1163 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1396 +#: bpython/urwid.py:1350 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:350 +#: bpython/curtsiesfrontend/repl.py:340 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:352 +#: bpython/curtsiesfrontend/repl.py:342 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:673 +#: bpython/curtsiesfrontend/repl.py:671 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:691 +#: bpython/curtsiesfrontend/repl.py:689 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:993 +#: bpython/curtsiesfrontend/repl.py:998 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1013 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1034 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1040 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1045 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1051 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 56ba969c6..1344567f1 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-01-05 13:08+0000\n" +"POT-Creation-Date: 2020-10-13 21:53+0200\n" "PO-Revision-Date: 2020-01-06 12:17+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: de\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:66 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " @@ -28,242 +28,239 @@ msgstr "" "Hinweis: Wenn bpython Argumente übergeben bekommt, die nicht verstanden " "werden, wird der normale Python Interpreter ausgeführt." -#: bpython/args.py:81 +#: bpython/args.py:78 msgid "Use CONFIG instead of default config file." msgstr "Verwende CONFIG antatt der standardmäßigen Konfigurationsdatei." -#: bpython/args.py:87 +#: bpython/args.py:84 msgid "Drop to bpython shell after running file instead of exiting." msgstr "Verbleibe in bpython nach dem Ausführen der Datei." -#: bpython/args.py:95 +#: bpython/args.py:92 msgid "Don't flush the output to stdout." msgstr "Gib Ausgabe beim Beenden nicht ernaut auf stdout aus." -#: bpython/args.py:101 +#: bpython/args.py:98 msgid "Print version and exit." msgstr "Zeige Versionsinformationen an und beende." -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "y" msgstr "j" -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "yes" msgstr "ja" -#: bpython/cli.py:1743 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "Rückgängig" -#: bpython/cli.py:1744 +#: bpython/cli.py:1696 msgid "Save" msgstr "Speichern" -#: bpython/cli.py:1745 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1746 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1747 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "Quellcode anzeigen" -#: bpython/cli.py:1994 +#: bpython/cli.py:1946 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." -msgstr "ACHTUNG: `bpython-cli` wird verwendet, die curses Implementierung von `bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv unterstützt und wird in einer zukünftigen Version entfernt werden." - -#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 -msgid "" -"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " -"been deprecated in version 0.19 and might disappear in a future version." -msgstr "ACHTUNG: `bpython` wird mit Python 2 verwendet. Diese Pythonversion wird seit Version 0.19 nicht mehr aktiv unterstützt und Unterstützung dafür wird in einer zukünftigen Version entfernt werden." +msgstr "" +"ACHTUNG: `bpython-cli` wird verwendet, die curses Implementierung von " +"`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " +"unterstützt und wird in einer zukünftigen Version entfernt werden." -#: bpython/curtsies.py:152 +#: bpython/curtsies.py:147 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:158 +#: bpython/curtsies.py:153 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:231 +#: bpython/history.py:226 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" -#: bpython/paste.py:95 +#: bpython/paste.py:86 msgid "Helper program not found." msgstr "Hilfsprogramm konnte nicht gefunden werden." -#: bpython/paste.py:97 +#: bpython/paste.py:88 msgid "Helper program could not be run." msgstr "Hilfsprogramm konnte nicht ausgeführt werden." -#: bpython/paste.py:103 +#: bpython/paste.py:94 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "Hilfsprogramm beendete mit Status %d." -#: bpython/paste.py:108 +#: bpython/paste.py:99 msgid "No output from helper program." msgstr "Keine Ausgabe von Hilfsprogramm vorhanden." -#: bpython/paste.py:115 +#: bpython/paste.py:106 msgid "Failed to recognize the helper program's output as an URL." msgstr "Konnte Ausgabe von Hilfsprogramm nicht verarbeiten." -#: bpython/repl.py:690 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:695 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" -#: bpython/repl.py:700 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/repl.py:702 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" -#: bpython/repl.py:841 +#: bpython/repl.py:823 msgid "Save to file (Esc to cancel): " msgstr "In Datei speichern (Esc um abzubrechen): " -#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 +#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 msgid "Save cancelled." msgstr "Speichern abgebrochen." -#: bpython/repl.py:857 +#: bpython/repl.py:839 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" -#: bpython/repl.py:865 +#: bpython/repl.py:847 msgid "overwrite" msgstr "überschreiben" -#: bpython/repl.py:867 +#: bpython/repl.py:849 msgid "append" msgstr "anhängen" -#: bpython/repl.py:879 bpython/repl.py:1192 +#: bpython/repl.py:861 bpython/repl.py:1171 #, python-format msgid "Error writing file '%s': %s" msgstr "Fehler beim Schreiben in Datei '%s': %s" -#: bpython/repl.py:881 +#: bpython/repl.py:863 #, python-format msgid "Saved to %s." msgstr "Nach %s gespeichert." -#: bpython/repl.py:887 +#: bpython/repl.py:869 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/repl.py:894 +#: bpython/repl.py:876 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/repl.py:896 +#: bpython/repl.py:878 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:905 +#: bpython/repl.py:887 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:907 +#: bpython/repl.py:889 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:915 +#: bpython/repl.py:897 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:921 +#: bpython/repl.py:903 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." -#: bpython/repl.py:925 +#: bpython/repl.py:907 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:934 +#: bpython/repl.py:916 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:939 +#: bpython/repl.py:921 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:977 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:985 bpython/repl.py:989 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "Rückgängigmachen abgebrochen" -#: bpython/repl.py:992 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1172 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" "Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " "werden? (j/N)" -#: bpython/repl.py:1202 +#: bpython/repl.py:1181 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" "bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " "Änderungen übernommen werden." -#: bpython/repl.py:1208 +#: bpython/repl.py:1187 #, python-format msgid "Error editing config file: %s" msgstr "Fehler beim Bearbeiten der Konfigurationsdatei: %s" -#: bpython/urwid.py:628 +#: bpython/urwid.py:618 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -#: bpython/urwid.py:1177 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "Führe twisted reactor aus." -#: bpython/urwid.py:1182 +#: bpython/urwid.py:1141 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "Wähle reactor aus (siehe --help-reactors). Impliziert --twisted." -#: bpython/urwid.py:1190 +#: bpython/urwid.py:1149 msgid "List available reactors for -r." msgstr "Liste verfügbare reactors für -r auf." -#: bpython/urwid.py:1195 +#: bpython/urwid.py:1154 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." @@ -271,62 +268,65 @@ msgstr "" "Auszuführendes twistd Plugin (starte twistd für eine Liste). Verwende " "\"--\" um Optionen an das Plugin zu übergeben." -#: bpython/urwid.py:1204 +#: bpython/urwid.py:1163 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1396 +#: bpython/urwid.py:1350 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." -msgstr "ACHTUNG: `bpython-urwid` wird verwendet, die curses Implementierung von `bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv unterstützt und wird in einer zukünftigen Version entfernt werden." +msgstr "" +"ACHTUNG: `bpython-urwid` wird verwendet, die curses Implementierung von " +"`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " +"unterstützt und wird in einer zukünftigen Version entfernt werden." -#: bpython/curtsiesfrontend/repl.py:350 +#: bpython/curtsiesfrontend/repl.py:340 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" -#: bpython/curtsiesfrontend/repl.py:352 +#: bpython/curtsiesfrontend/repl.py:342 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#: bpython/curtsiesfrontend/repl.py:673 +#: bpython/curtsiesfrontend/repl.py:671 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" -#: bpython/curtsiesfrontend/repl.py:691 +#: bpython/curtsiesfrontend/repl.py:689 #, python-format msgid "Reloaded at %s because %s modified." msgstr "Bei %s neugeladen, da %s modifiziert wurde." -#: bpython/curtsiesfrontend/repl.py:993 +#: bpython/curtsiesfrontend/repl.py:998 msgid "Session not reevaluated because it was not edited" msgstr "Die Sitzung wurde nicht neu ausgeführt, da sie nicht berabeitet wurde" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1013 msgid "Session not reevaluated because saved file was blank" msgstr "Die Sitzung wurde nicht neu ausgeführt, da die gespeicherte Datei leer war" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session edited and reevaluated" msgstr "Sitzung bearbeitet und neu ausgeführt" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1034 #, python-format msgid "Reloaded at %s by user." msgstr "Bei %s vom Benutzer neu geladen." -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1040 msgid "Auto-reloading deactivated." msgstr "Automatisches Neuladen deaktiviert." -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1045 msgid "Auto-reloading active, watching for file changes..." msgstr "Automatisches Neuladen ist aktiv; beobachte Dateiänderungen..." -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1051 msgid "Auto-reloading not available because watchdog not installed." msgstr "" "Automatisches Neuladen ist nicht verfügbar da watchdog nicht installiert " diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 331c672ac..57057a420 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-01-05 13:08+0000\n" +"POT-Creation-Date: 2020-10-13 21:53+0200\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: es_ES\n" @@ -18,308 +18,302 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:66 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:81 +#: bpython/args.py:78 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:87 +#: bpython/args.py:84 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:95 +#: bpython/args.py:92 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:101 +#: bpython/args.py:98 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "y" msgstr "s" -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "yes" msgstr "si" -#: bpython/cli.py:1743 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "" -#: bpython/cli.py:1744 +#: bpython/cli.py:1696 msgid "Save" msgstr "" -#: bpython/cli.py:1745 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1746 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1747 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "" -#: bpython/cli.py:1994 +#: bpython/cli.py:1946 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 -msgid "" -"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " -"been deprecated in version 0.19 and might disappear in a future version." -msgstr "" - -#: bpython/curtsies.py:152 +#: bpython/curtsies.py:147 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:158 +#: bpython/curtsies.py:153 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:231 +#: bpython/history.py:226 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:95 +#: bpython/paste.py:86 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:97 +#: bpython/paste.py:88 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:103 +#: bpython/paste.py:94 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:108 +#: bpython/paste.py:99 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:115 +#: bpython/paste.py:106 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:690 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:695 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:700 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:702 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:841 +#: bpython/repl.py:823 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 +#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:857 +#: bpython/repl.py:839 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:865 +#: bpython/repl.py:847 msgid "overwrite" msgstr "" -#: bpython/repl.py:867 +#: bpython/repl.py:849 msgid "append" msgstr "" -#: bpython/repl.py:879 bpython/repl.py:1192 +#: bpython/repl.py:861 bpython/repl.py:1171 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:881 +#: bpython/repl.py:863 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:887 +#: bpython/repl.py:869 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:876 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:896 +#: bpython/repl.py:878 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:905 +#: bpython/repl.py:887 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:907 +#: bpython/repl.py:889 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:915 +#: bpython/repl.py:897 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:921 +#: bpython/repl.py:903 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:925 +#: bpython/repl.py:907 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:934 +#: bpython/repl.py:916 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:939 +#: bpython/repl.py:921 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:977 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:985 bpython/repl.py:989 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:992 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1172 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1202 +#: bpython/repl.py:1181 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1208 +#: bpython/repl.py:1187 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:628 +#: bpython/urwid.py:618 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra el " "código fuente" -#: bpython/urwid.py:1177 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1182 +#: bpython/urwid.py:1141 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1190 +#: bpython/urwid.py:1149 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1195 +#: bpython/urwid.py:1154 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1204 +#: bpython/urwid.py:1163 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1396 +#: bpython/urwid.py:1350 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:350 +#: bpython/curtsiesfrontend/repl.py:340 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:352 +#: bpython/curtsiesfrontend/repl.py:342 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:673 +#: bpython/curtsiesfrontend/repl.py:671 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:691 +#: bpython/curtsiesfrontend/repl.py:689 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:993 +#: bpython/curtsiesfrontend/repl.py:998 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1013 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1034 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1040 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1045 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1051 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index bc03d38d5..73330837f 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-01-05 13:08+0000\n" +"POT-Creation-Date: 2020-10-13 21:53+0200\n" "PO-Revision-Date: 2019-09-22 22:58+0200\n" "Last-Translator: Sebastian Ramacher \n" "Language: fr_FR\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:66 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " @@ -27,242 +27,236 @@ msgstr "" "NOTE: Si bpython ne reconnaît pas un des arguments fournis, " "l'interpréteur Python classique sera lancé" -#: bpython/args.py:81 +#: bpython/args.py:78 msgid "Use CONFIG instead of default config file." msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." -#: bpython/args.py:87 +#: bpython/args.py:84 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" "Aller dans le shell bpython après l'exécution du fichier au lieu de " "quitter." -#: bpython/args.py:95 +#: bpython/args.py:92 msgid "Don't flush the output to stdout." msgstr "Ne pas purger la sortie vers stdout." -#: bpython/args.py:101 +#: bpython/args.py:98 msgid "Print version and exit." msgstr "Afficher la version et quitter." -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "y" msgstr "o" -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "yes" msgstr "oui" -#: bpython/cli.py:1743 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "Rembobiner" -#: bpython/cli.py:1744 +#: bpython/cli.py:1696 msgid "Save" msgstr "Sauvegarder" -#: bpython/cli.py:1745 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1746 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1747 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "Montrer le code source" -#: bpython/cli.py:1994 +#: bpython/cli.py:1946 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 -msgid "" -"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " -"been deprecated in version 0.19 and might disappear in a future version." -msgstr "" - -#: bpython/curtsies.py:152 +#: bpython/curtsies.py:147 msgid "log debug messages to bpython.log" msgstr "logger les messages de debug dans bpython.log" -#: bpython/curtsies.py:158 +#: bpython/curtsies.py:153 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:231 +#: bpython/history.py:226 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" -#: bpython/paste.py:95 +#: bpython/paste.py:86 msgid "Helper program not found." msgstr "programme externe non trouvé." -#: bpython/paste.py:97 +#: bpython/paste.py:88 msgid "Helper program could not be run." msgstr "impossible de lancer le programme externe." -#: bpython/paste.py:103 +#: bpython/paste.py:94 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "le programme externe a renvoyé un statut de sortie différent de zéro %d." -#: bpython/paste.py:108 +#: bpython/paste.py:99 msgid "No output from helper program." msgstr "pas de sortie du programme externe." -#: bpython/paste.py:115 +#: bpython/paste.py:106 msgid "Failed to recognize the helper program's output as an URL." msgstr "la sortie du programme externe ne correspond pas à une URL." -#: bpython/repl.py:690 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:695 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "Impossible de récupérer le source: %s" -#: bpython/repl.py:700 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "Impossible d'accéder au source de %r" -#: bpython/repl.py:702 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "Pas de code source trouvé pour %s" -#: bpython/repl.py:841 +#: bpython/repl.py:823 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 +#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:857 +#: bpython/repl.py:839 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:865 +#: bpython/repl.py:847 msgid "overwrite" msgstr "" -#: bpython/repl.py:867 +#: bpython/repl.py:849 msgid "append" msgstr "" -#: bpython/repl.py:879 bpython/repl.py:1192 +#: bpython/repl.py:861 bpython/repl.py:1171 #, python-format msgid "Error writing file '%s': %s" msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" -#: bpython/repl.py:881 +#: bpython/repl.py:863 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:887 +#: bpython/repl.py:869 msgid "No clipboard available." msgstr "Pas de presse-papier disponible." -#: bpython/repl.py:894 +#: bpython/repl.py:876 msgid "Could not copy to clipboard." msgstr "Impossible de copier vers le presse-papier." -#: bpython/repl.py:896 +#: bpython/repl.py:878 msgid "Copied content to clipboard." msgstr "Contenu copié vers le presse-papier." -#: bpython/repl.py:905 +#: bpython/repl.py:887 msgid "Pastebin buffer? (y/N) " msgstr "Tampon Pastebin ? (o/N) " -#: bpython/repl.py:907 +#: bpython/repl.py:889 msgid "Pastebin aborted." msgstr "Pastebin abandonné." -#: bpython/repl.py:915 +#: bpython/repl.py:897 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" -#: bpython/repl.py:921 +#: bpython/repl.py:903 msgid "Posting data to pastebin..." msgstr "Envoi des donnés à pastebin..." -#: bpython/repl.py:925 +#: bpython/repl.py:907 #, python-format msgid "Upload failed: %s" msgstr "Echec du téléchargement: %s" -#: bpython/repl.py:934 +#: bpython/repl.py:916 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "URL Pastebin: %s - URL de suppression: %s" -#: bpython/repl.py:939 +#: bpython/repl.py:921 #, python-format msgid "Pastebin URL: %s" msgstr "URL Pastebin: %s" -#: bpython/repl.py:977 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:985 bpython/repl.py:989 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:992 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1172 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" -#: bpython/repl.py:1202 +#: bpython/repl.py:1181 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1208 +#: bpython/repl.py:1187 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:628 +#: bpython/urwid.py:618 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> " "Montrer Source " -#: bpython/urwid.py:1177 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "Lancer le reactor twisted." -#: bpython/urwid.py:1182 +#: bpython/urwid.py:1141 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." -#: bpython/urwid.py:1190 +#: bpython/urwid.py:1149 msgid "List available reactors for -r." msgstr "Lister les reactors disponibles pour -r." -#: bpython/urwid.py:1195 +#: bpython/urwid.py:1154 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." @@ -270,62 +264,62 @@ msgstr "" "plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" " "pour donner plus d'options au plugin." -#: bpython/urwid.py:1204 +#: bpython/urwid.py:1163 msgid "Port to run an eval server on (forces Twisted)." msgstr "Port pour lancer un server eval (force Twisted)." -#: bpython/urwid.py:1396 +#: bpython/urwid.py:1350 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:350 +#: bpython/curtsiesfrontend/repl.py:340 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" -#: bpython/curtsiesfrontend/repl.py:352 +#: bpython/curtsiesfrontend/repl.py:342 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." -#: bpython/curtsiesfrontend/repl.py:673 +#: bpython/curtsiesfrontend/repl.py:671 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" -#: bpython/curtsiesfrontend/repl.py:691 +#: bpython/curtsiesfrontend/repl.py:689 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:993 +#: bpython/curtsiesfrontend/repl.py:998 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1013 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1034 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1040 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1045 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1051 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index f9a471d9d..7c94712da 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-01-05 13:08+0000\n" +"POT-Creation-Date: 2020-10-13 21:53+0200\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: it_IT\n" @@ -18,306 +18,300 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:66 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:81 +#: bpython/args.py:78 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:87 +#: bpython/args.py:84 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:95 +#: bpython/args.py:92 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:101 +#: bpython/args.py:98 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "y" msgstr "s" -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "yes" msgstr "si" -#: bpython/cli.py:1743 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "" -#: bpython/cli.py:1744 +#: bpython/cli.py:1696 msgid "Save" msgstr "" -#: bpython/cli.py:1745 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1746 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1747 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "" -#: bpython/cli.py:1994 +#: bpython/cli.py:1946 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 -msgid "" -"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " -"been deprecated in version 0.19 and might disappear in a future version." -msgstr "" - -#: bpython/curtsies.py:152 +#: bpython/curtsies.py:147 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:158 +#: bpython/curtsies.py:153 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:231 +#: bpython/history.py:226 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:95 +#: bpython/paste.py:86 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:97 +#: bpython/paste.py:88 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:103 +#: bpython/paste.py:94 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:108 +#: bpython/paste.py:99 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:115 +#: bpython/paste.py:106 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:690 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:695 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:700 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:702 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:841 +#: bpython/repl.py:823 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 +#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:857 +#: bpython/repl.py:839 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:865 +#: bpython/repl.py:847 msgid "overwrite" msgstr "" -#: bpython/repl.py:867 +#: bpython/repl.py:849 msgid "append" msgstr "" -#: bpython/repl.py:879 bpython/repl.py:1192 +#: bpython/repl.py:861 bpython/repl.py:1171 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:881 +#: bpython/repl.py:863 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:887 +#: bpython/repl.py:869 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:876 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:896 +#: bpython/repl.py:878 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:905 +#: bpython/repl.py:887 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:907 +#: bpython/repl.py:889 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:915 +#: bpython/repl.py:897 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:921 +#: bpython/repl.py:903 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:925 +#: bpython/repl.py:907 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:934 +#: bpython/repl.py:916 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:939 +#: bpython/repl.py:921 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:977 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:985 bpython/repl.py:989 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:992 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1172 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1202 +#: bpython/repl.py:1181 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1208 +#: bpython/repl.py:1187 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:628 +#: bpython/urwid.py:618 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" -#: bpython/urwid.py:1177 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1182 +#: bpython/urwid.py:1141 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1190 +#: bpython/urwid.py:1149 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1195 +#: bpython/urwid.py:1154 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1204 +#: bpython/urwid.py:1163 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1396 +#: bpython/urwid.py:1350 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:350 +#: bpython/curtsiesfrontend/repl.py:340 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:352 +#: bpython/curtsiesfrontend/repl.py:342 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:673 +#: bpython/curtsiesfrontend/repl.py:671 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:691 +#: bpython/curtsiesfrontend/repl.py:689 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:993 +#: bpython/curtsiesfrontend/repl.py:998 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1013 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1034 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1040 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1045 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1051 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index fabdfd591..eab83d1ae 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-01-05 13:08+0000\n" +"POT-Creation-Date: 2020-10-13 21:53+0200\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: nl_NL\n" @@ -18,306 +18,300 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:66 +#: bpython/args.py:63 msgid "" "Usage: %prog [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:81 +#: bpython/args.py:78 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:87 +#: bpython/args.py:84 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:95 +#: bpython/args.py:92 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:101 +#: bpython/args.py:98 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "y" msgstr "j" -#: bpython/cli.py:324 bpython/urwid.py:561 +#: bpython/cli.py:315 bpython/urwid.py:551 msgid "yes" msgstr "ja" -#: bpython/cli.py:1743 +#: bpython/cli.py:1695 msgid "Rewind" msgstr "" -#: bpython/cli.py:1744 +#: bpython/cli.py:1696 msgid "Save" msgstr "" -#: bpython/cli.py:1745 +#: bpython/cli.py:1697 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1746 +#: bpython/cli.py:1698 msgid "Pager" msgstr "" -#: bpython/cli.py:1747 +#: bpython/cli.py:1699 msgid "Show Source" msgstr "" -#: bpython/cli.py:1994 +#: bpython/cli.py:1946 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/cli.py:2003 bpython/curtsies.py:208 bpython/urwid.py:1405 -msgid "" -"WARNING: You are using `bpython` on Python 2. Support for Python 2 has " -"been deprecated in version 0.19 and might disappear in a future version." -msgstr "" - -#: bpython/curtsies.py:152 +#: bpython/curtsies.py:147 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:158 +#: bpython/curtsies.py:153 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:231 +#: bpython/history.py:226 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:95 +#: bpython/paste.py:86 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:97 +#: bpython/paste.py:88 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:103 +#: bpython/paste.py:94 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:108 +#: bpython/paste.py:99 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:115 +#: bpython/paste.py:106 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:690 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:695 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:700 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:702 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:841 +#: bpython/repl.py:823 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:843 bpython/repl.py:846 bpython/repl.py:870 +#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:857 +#: bpython/repl.py:839 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:865 +#: bpython/repl.py:847 msgid "overwrite" msgstr "" -#: bpython/repl.py:867 +#: bpython/repl.py:849 msgid "append" msgstr "" -#: bpython/repl.py:879 bpython/repl.py:1192 +#: bpython/repl.py:861 bpython/repl.py:1171 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:881 +#: bpython/repl.py:863 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:887 +#: bpython/repl.py:869 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:876 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:896 +#: bpython/repl.py:878 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:905 +#: bpython/repl.py:887 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:907 +#: bpython/repl.py:889 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:915 +#: bpython/repl.py:897 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:921 +#: bpython/repl.py:903 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:925 +#: bpython/repl.py:907 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:934 +#: bpython/repl.py:916 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:939 +#: bpython/repl.py:921 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:977 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:985 bpython/repl.py:989 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:992 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1172 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1202 +#: bpython/repl.py:1181 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1208 +#: bpython/repl.py:1187 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:628 +#: bpython/urwid.py:618 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" -#: bpython/urwid.py:1177 +#: bpython/urwid.py:1136 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1182 +#: bpython/urwid.py:1141 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1190 +#: bpython/urwid.py:1149 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1195 +#: bpython/urwid.py:1154 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1204 +#: bpython/urwid.py:1163 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1396 +#: bpython/urwid.py:1350 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:350 +#: bpython/curtsiesfrontend/repl.py:340 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:352 +#: bpython/curtsiesfrontend/repl.py:342 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:673 +#: bpython/curtsiesfrontend/repl.py:671 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:691 +#: bpython/curtsiesfrontend/repl.py:689 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:993 +#: bpython/curtsiesfrontend/repl.py:998 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1013 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1034 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1040 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1045 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1051 msgid "Auto-reloading not available because watchdog not installed." msgstr "" From 07f4fe34438ed7090baa31f8ac65316dca873153 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 22:00:32 +0200 Subject: [PATCH 1046/1650] Remove Python 2.7 from the documentation --- README.rst | 14 -------------- doc/sphinx/source/contributing.rst | 20 +++++++++----------- doc/sphinx/source/releases.rst | 3 +-- doc/sphinx/source/tips.rst | 3 +-- 4 files changed, 11 insertions(+), 29 deletions(-) diff --git a/README.rst b/README.rst index 8e13045af..88924e7da 100644 --- a/README.rst +++ b/README.rst @@ -100,23 +100,10 @@ Dependencies * curtsies >= 0.3.0 * greenlet * Sphinx != 1.1.2 (optional, for the documentation) -* mock (optional, for the testsuite) * babel (optional, for internationalization) * watchdog (optional, for monitoring imported modules for changes) * jedi (optional, for experimental multiline completion) -Python 2 before 2.7.7 ---------------------- -If you are using Python 2 before 2.7.7, the following dependency is also -required: - -* requests[security] - -cffi ----- -If you have problems installing cffi, which is needed by OpenSSL, please take a -look at `cffi docs`_. - bpython-urwid ------------- ``bpython-urwid`` requires the following additional packages: @@ -188,7 +175,6 @@ may be interested to try. .. _ipython: https://ipython.org/ .. _homepage: http://www.bpython-interpreter.org .. _full documentation: http://docs.bpython-interpreter.org/ -.. _cffi docs: https://cffi.readthedocs.org/en/release-0.8/#macos-x .. _issues tracker: http://github.com/bpython/bpython/issues/ .. _pip: https://pip.pypa.io/en/latest/index.html .. _project homepage: http://bpython-interpreter.org diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index f97157342..06e656d49 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -17,8 +17,8 @@ the time of day. Getting your development environment set up ------------------------------------------- -bpython supports Python 2.7, 3.6 and newer. The code is compatible with all -supported versions without the need to run post processing like `2to3`. +bpython supports Python 3.6 and newer. The code is compatible with all +supported versions. Using a virtual environment is probably a good idea. Create a virtual environment with @@ -47,7 +47,7 @@ Next install your development copy of bpython and its dependencies: # install optional dependencies $ pip install watchdog urwid # development dependencies - $ pip install sphinx mock nose + $ pip install sphinx nose # this runs your modified copy of bpython! $ bpython @@ -60,14 +60,12 @@ Next install your development copy of bpython and its dependencies: .. code-block:: bash - $ sudp apt-get install python-greenlet python-pygments python-requests - $ sudo apt-get install python-watchdog python-urwid - $ sudo apt-get install python-sphinx python-mock python-nose + $ sudp apt install python3-greenlet python3-pygments python3-requests + $ sudo apt install python3-watchdog python3-urwid + $ sudo apt install python3-sphinx python3-nose - Remember to replace ``python`` with ``python3`` in every package name if - you intend to develop with Python 3. You also need to run `virtualenv` with - `--system-site-packages` packages, if you want to use the packages provided - by your distribution. + You also need to run `virtualenv` with `--system-site-packages` packages, if + you want to use the packages provided by your distribution. .. note:: @@ -76,7 +74,7 @@ Next install your development copy of bpython and its dependencies: .. code-block:: bash - $ sudo apt-get install gcc python-dev + $ sudo apt install gcc python3-dev As a first dev task, I recommend getting `bpython` to print your name every time you hit a specific key. diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst index 36a4a9bc8..1f723f592 100644 --- a/doc/sphinx/source/releases.rst +++ b/doc/sphinx/source/releases.rst @@ -45,7 +45,7 @@ A checklist to perform some manual tests before a release: Check that all of the following work before a release: -* Runs under Python 2.7, 3.4, 3.5 and 3.6. +* Runs under Python 3.6 - 3.9 * Save * Rewind * Pastebin @@ -59,4 +59,3 @@ Check that all of the following work before a release: * Command line arguments correctly passed to scripts * Delegate to standard Python appropriately * Update CHANGELOG -* Update __version__ diff --git a/doc/sphinx/source/tips.rst b/doc/sphinx/source/tips.rst index 925097a87..f2519b405 100644 --- a/doc/sphinx/source/tips.rst +++ b/doc/sphinx/source/tips.rst @@ -21,8 +21,7 @@ equivalent file. Where the `~/python/bpython`-path is the path to where your bpython source code resides. -You can of course add multiple aliases, so you can run bpython with 2.7 and the -3 series. +You can of course add multiple aliases. .. note:: From 09d176b6eb483f08f3b3772ea1f561c95e5ef9e4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 22:05:42 +0200 Subject: [PATCH 1047/1650] Include LICENSE in wheel --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 2a9acf13d..c2d7a6fc3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [bdist_wheel] universal = 1 + +[metadata] +license_files = LICENSE From bd880f9e3f22345313d9aaa8992b53fb5b8097da Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 22:05:54 +0200 Subject: [PATCH 1048/1650] No longer provide universal wheels --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index c2d7a6fc3..8183238ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,2 @@ -[bdist_wheel] -universal = 1 - [metadata] license_files = LICENSE From 2c3c84f8bc99d431bb3a0fe6c4c54250a7c6e86a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 22:47:31 +0200 Subject: [PATCH 1049/1650] Simplify parsekeywordpairs --- bpython/inspection.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 0014c4f84..b8fc9b714 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -51,7 +51,7 @@ class AttrCleaner: """A context manager that tries to make an object not exhibit side-effects - on attribute lookup.""" + on attribute lookup.""" def __init__(self, obj): self.obj = obj @@ -126,9 +126,9 @@ def parsekeywordpairs(signature): continue if token is Token.Punctuation: - if value in ["(", "{", "["]: + if value in ("(", "{", "["): parendepth += 1 - elif value in [")", "}", "]"]: + elif value in (")", "}", "]"): parendepth -= 1 elif value == ":" and parendepth == -1: # End of signature reached @@ -143,11 +143,7 @@ def parsekeywordpairs(signature): if value and (parendepth > 0 or value.strip()): substack.append(value) - d = {} - for item in stack: - if len(item) >= 3: - d[item[0]] = "".join(item[2:]) - return d + return {item[0]: "".join(item[2:] for item in stack if len(item) >= 3)} def fixlongargs(f, argspec): From 76f069b626d55a4d91bca5dc4147adda143c40f8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 22:49:48 +0200 Subject: [PATCH 1050/1650] Fix placement of ) --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index b8fc9b714..4e3159585 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -143,7 +143,7 @@ def parsekeywordpairs(signature): if value and (parendepth > 0 or value.strip()): substack.append(value) - return {item[0]: "".join(item[2:] for item in stack if len(item) >= 3)} + return {item[0]: "".join(item[2:]) for item in stack if len(item) >= 3} def fixlongargs(f, argspec): From 9faba3ad1cd9d4e035a8a5614015d35396354f59 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 22:56:42 +0200 Subject: [PATCH 1051/1650] Remove bpython.inspection.is_callable --- bpython/autocomplete.py | 2 +- bpython/inspection.py | 5 ----- bpython/test/test_inspection.py | 8 -------- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 4e062481a..b0508bc9f 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -632,6 +632,6 @@ def get_completer_bpython(cursor_offset, line, **kwargs): def _callable_postfix(value, word): """rlcompleter's _callable_postfix done right.""" - if inspection.is_callable(value): + if callable(value): word += "(" return word diff --git a/bpython/inspection.py b/bpython/inspection.py index 4e3159585..8e430deee 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -21,7 +21,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - import inspect import keyword import pydoc @@ -261,10 +260,6 @@ def is_eval_safe_name(string): ) -def is_callable(obj): - return callable(obj) - - def get_argspec_from_signature(f): """Get callable signature from inspect.signature in argspec format. diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 9e0892437..6bf6e7793 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -37,14 +37,6 @@ def method(self): class TestInspection(unittest.TestCase): - def test_is_callable(self): - self.assertTrue(inspection.is_callable(spam)) - self.assertTrue(inspection.is_callable(Callable)) - self.assertTrue(inspection.is_callable(Callable())) - self.assertFalse(inspection.is_callable(Noncallable())) - self.assertFalse(inspection.is_callable(None)) - self.assertTrue(inspection.is_callable(CallableMethod().method)) - def test_parsekeywordpairs(self): # See issue #109 def fails(spam=["-a", "-b"]): From 8f7f02597eda298349c7e7b70fed3daed6a2ac44 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 13 Oct 2020 23:03:13 +0200 Subject: [PATCH 1052/1650] Fix ConfigParser.read_fp deprecation warning --- bpython/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index ff7c6da3b..066ac7085 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -305,7 +305,7 @@ def get_key_no_doublebind(command): def load_theme(struct, path, colors, default_colors): theme = ConfigParser() with open(path) as f: - theme.readfp(f) + theme.read_file(f) for k, v in chain(theme.items("syntax"), theme.items("interface")): if theme.has_option("syntax", k): colors[k] = theme.get("syntax", k) From 5a68b0e3c6c6b2836f2b4357643ff6b2da6ace8b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 14 Oct 2020 11:26:45 +0200 Subject: [PATCH 1053/1650] Generate bpython._version for tarballs from github (fixes #643) --- setup.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/setup.py b/setup.py index 9e55b0077..bf6f5e420 100755 --- a/setup.py +++ b/setup.py @@ -105,6 +105,18 @@ def git_describe_to_python_version(version): except OSError: pass +if version == "unknown": + # get version from directory name (tarballs downloaded from tags) + # directories are named bpython-X.Y-release in this case + basename = os.path.basename(os.path.dirname(__file__)) + basename_components = basename.split("-") + if ( + len(basename_components) == 3 + and basename_components[0] == "bpython" + and basename_components[2] == "release" + ): + version = basename_components[1] + with open(version_file, "w") as vf: vf.write("# Auto-generated file, do not edit!\n") vf.write(f"__version__ = '{version}'\n") From 5d25fa9b2d4bcd83aa8f0c86f8281c827c486cff Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 14 Oct 2020 21:32:17 +0200 Subject: [PATCH 1054/1650] Remove unused bpython.inspection.get_encoding_comment --- bpython/inspection.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 8e430deee..5009aba95 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -329,15 +329,6 @@ def get_encoding(obj): return "ascii" -def get_encoding_comment(source): - """Returns encoding line without the newline, or None is not found""" - for line in source.splitlines()[:2]: - m = get_encoding_line_re.search(line) - if m: - return m.group(0) - return None - - def get_encoding_file(fname): """Try to obtain encoding information from a Python source file.""" with open(fname, "rt", encoding="ascii", errors="ignore") as f: From 1cfba80da898c54334521318e232f0d31cac7051 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 14 Oct 2020 21:42:32 +0200 Subject: [PATCH 1055/1650] Fix jedi 0.16.0 deprecation warnings Also bump the dependency to >= 0.16.0 --- .travis.install.sh | 2 +- bpython/autocomplete.py | 4 ++-- bpython/test/test_autocomplete.py | 2 +- setup.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index 340048d87..43392c336 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -10,7 +10,7 @@ if [[ $RUN == nosetests ]]; then # filewatch specific dependencies pip install watchdog # jedi specific dependencies - pip install jedi + pip install 'jedi >= 0.16' # translation specific dependencies pip install babel # build and install diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index b0508bc9f..a06776f6e 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -520,9 +520,9 @@ def matches(self, cursor_offset, line, **kwargs): try: script = jedi.Script( - history, len(history.splitlines()), cursor_offset, "fake.py" + history, path="fake.py" ) - completions = script.completions() + completions = script.complete(len(history.splitlines()), cursor_offset) except (jedi.NotFoundError, IndexError, KeyError): # IndexError for #483 # KeyError for #544 diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 65beb0118..282435774 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -367,7 +367,7 @@ def matches_from_completions( ): with mock.patch("bpython.autocomplete.jedi.Script") as Script: script = Script.return_value - script.completions.return_value = completions + script.complete.return_value = completions com = autocomplete.MultilineJediCompletion() return com.matches( cursor, line, current_block=block, history=history diff --git a/setup.py b/setup.py index bf6f5e420..c5e37c0f0 100755 --- a/setup.py +++ b/setup.py @@ -241,7 +241,7 @@ def initialize_options(self): extras_require = { "urwid": ["urwid"], "watch": ["watchdog"], - "jedi": ["jedi"], + "jedi": ["jedi >=0.16"], } packages = [ From 5201a49d26d95ef23c66681d5d7ab7e1f8b87890 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 14 Oct 2020 21:46:03 +0200 Subject: [PATCH 1056/1650] Bump copyright year --- bpython/args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/args.py b/bpython/args.py index 578e46836..7300abeb2 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -113,7 +113,7 @@ def parse(args, extras=None, ignore_stdin=False): if options.version: print(version_banner()) print( - "(C) 2008-2016 Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al. " + "(C) 2008-2020 Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al. " "See AUTHORS for detail." ) raise SystemExit From b9f081c5c821f57f03b8245d4091bc173a859dac Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 20:22:10 +0200 Subject: [PATCH 1057/1650] Replace imp with importlib (fixes #857) --- bpython/args.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 7300abeb2..aceef5c00 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -4,7 +4,7 @@ import code -import imp +import importlib.util import os import sys from optparse import OptionParser, OptionGroup @@ -82,7 +82,7 @@ def parse(args, extras=None, ignore_stdin=False): "-i", action="store_true", help=_( - "Drop to bpython shell after running file " "instead of exiting." + "Drop to bpython shell after running file instead of exiting." ), ) parser.add_option( @@ -138,7 +138,8 @@ def exec_code(interpreter, args): source = sourcefile.read() old_argv, sys.argv = sys.argv, args sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) - mod = imp.new_module("__console__") + spec = importlib.util.spec_from_loader("__console__", loader=None) + mod = importlib.util.module_from_spec(spec) sys.modules["__console__"] = mod interpreter.locals = mod.__dict__ interpreter.locals["__file__"] = args[0] From ddd6329793c76e5ef518d405d86bc7be4dc748f2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 20:37:12 +0200 Subject: [PATCH 1058/1650] Update CHANGELOG --- CHANGELOG | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e380b2714..94e12f4ad 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,19 +5,19 @@ Changelog ---- General information: - * Support for Python 2 has been dropped. New features: +* #643: Provide bpython._version if built from Github tarballs Fixes: +* #857: Replace remaining use of deprecated imp with importlib 0.20 ---- General information: - -* The next release of bpython (0.20) will drop support for Python 2. +* The next release of bpython (0.20) will drop support for Python 2. * Support for Python 3.9 has been added. Support for Python 3.5 has been dropped. From 3dc73aeaaf3786862d00d427ae500c0e76fd6139 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 20:37:32 +0200 Subject: [PATCH 1059/1650] Remove unnecessary modes --- bpython/history.py | 2 +- bpython/inspection.py | 2 +- bpython/repl.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/history.py b/bpython/history.py index 0f1de12a6..61c476e85 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -170,7 +170,7 @@ def reset(self): def load(self, filename, encoding): with open( - filename, "r", encoding=encoding, errors="ignore" + filename, encoding=encoding, errors="ignore" ) as hfile: with FileLock(hfile, filename=filename): self.entries = self.load_from(hfile) diff --git a/bpython/inspection.py b/bpython/inspection.py index 5009aba95..bf039e612 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -331,7 +331,7 @@ def get_encoding(obj): def get_encoding_file(fname): """Try to obtain encoding information from a Python source file.""" - with open(fname, "rt", encoding="ascii", errors="ignore") as f: + with open(fname, encoding="ascii", errors="ignore") as f: for unused in range(2): line = f.readline() match = get_encoding_line_re.search(line) diff --git a/bpython/repl.py b/bpython/repl.py index 0d5e483a6..e62ec677c 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -480,7 +480,7 @@ def startup(self): filename = os.environ.get("PYTHONSTARTUP") if filename: encoding = inspection.get_encoding_file(filename) - with open(filename, "rt", encoding=encoding) as f: + with open(filename, encoding=encoding) as f: source = f.read() self.interp.runsource(source, filename, "exec", encode=False) From 8f83fa54abbc74243fb2659a83b40b346397affe Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 20:41:36 +0200 Subject: [PATCH 1060/1650] pyproject.toml: upgrade target_version to 3.6 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ad3cc4caf..a4df32f85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.black] line-length = 80 -target_version = ["py27"] +target_version = ["py36"] include = '\.pyi?$' exclude = ''' /( From 80539f1c4cbd9c83d83fe5b825c7cda88fd6d3a6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 20:46:53 +0200 Subject: [PATCH 1061/1650] Apply black --- bpdb/__init__.py | 4 ++-- bpython/args.py | 4 +--- bpython/autocomplete.py | 23 +++++++-------------- bpython/cli.py | 8 +++---- bpython/config.py | 14 ++++++++----- bpython/curtsies.py | 4 ++-- bpython/curtsiesfrontend/manual_readline.py | 8 ++----- bpython/curtsiesfrontend/repl.py | 23 ++++++++++----------- bpython/curtsiesfrontend/sitefix.py | 4 +--- bpython/filelock.py | 9 +++----- bpython/history.py | 4 +--- bpython/pager.py | 1 + bpython/repl.py | 22 +++++++++++--------- bpython/simpleeval.py | 7 ++----- bpython/test/test_autocomplete.py | 15 +++++--------- bpython/test/test_repl.py | 2 +- bpython/test/test_simpleeval.py | 4 +++- bpython/translations/__init__.py | 3 +++ bpython/urwid.py | 6 ++---- 19 files changed, 73 insertions(+), 92 deletions(-) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index ea2a4cdee..7c2be1717 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -34,8 +34,8 @@ def set_trace(): - """ Just like pdb.set_trace(), a helper function that creates - a debugger instance and sets the trace. """ + """Just like pdb.set_trace(), a helper function that creates + a debugger instance and sets the trace.""" debugger = BPdb() debugger.set_trace(sys._getframe().f_back) diff --git a/bpython/args.py b/bpython/args.py index aceef5c00..6eef37759 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -81,9 +81,7 @@ def parse(args, extras=None, ignore_stdin=False): "--interactive", "-i", action="store_true", - help=_( - "Drop to bpython shell after running file instead of exiting." - ), + help=_("Drop to bpython shell after running file instead of exiting."), ) parser.add_option( "--quiet", diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a06776f6e..9b4f72dd2 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -170,7 +170,7 @@ def matches(self, cursor_offset, line, **kwargs): * determine whether suggestions should be `shown_before_tab` * `substitute(cur, line, match)` in a match for what's found with `target` - """ + """ raise NotImplementedError def locate(self, cursor_offset, line): @@ -315,8 +315,7 @@ def format(self, word): return after_last_dot(word) def attr_matches(self, text, namespace): - """Taken from rlcompleter.py and bent to my will. - """ + """Taken from rlcompleter.py and bent to my will.""" m = self.attr_matches_re.match(text) if not m: @@ -377,9 +376,7 @@ def matches(self, cursor_offset, line, **kwargs): return None if isinstance(obj, dict) and obj.keys(): matches = { - f"{k!r}]" - for k in obj.keys() - if repr(k).startswith(r.word) + f"{k!r}]" for k in obj.keys() if repr(k).startswith(r.word) } return matches if matches else None else: @@ -462,9 +459,7 @@ def matches(self, cursor_offset, line, **kwargs): if isinstance(name, str) and name.startswith(r.word) } matches.update( - name + "=" - for name in argspec[1][4] - if name.startswith(r.word) + name + "=" for name in argspec[1][4] if name.startswith(r.word) ) return matches if matches else None @@ -519,10 +514,10 @@ def matches(self, cursor_offset, line, **kwargs): history = "\n".join(history) + "\n" + line try: - script = jedi.Script( - history, path="fake.py" + script = jedi.Script(history, path="fake.py") + completions = script.complete( + len(history.splitlines()), cursor_offset ) - completions = script.complete(len(history.splitlines()), cursor_offset) except (jedi.NotFoundError, IndexError, KeyError): # IndexError for #483 # KeyError for #544 @@ -566,9 +561,7 @@ def matches(self, cursor_offset, line, **kwargs): cursor_offset, line, ) - results = super().matches( - cursor_offset, line, history=history - ) + results = super().matches(cursor_offset, line, history=history) return results else: return None diff --git a/bpython/cli.py b/bpython/cli.py index 99a1b87f9..cd9a20904 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -772,9 +772,7 @@ def mkargspec(self, topline, in_arg, down): if _args: if args: self.list_win.addstr(", ", punctuation_colpair) - self.list_win.addstr( - f"*{_args}", get_colpair(self.config, "token") - ) + self.list_win.addstr(f"*{_args}", get_colpair(self.config, "token")) if kwonly: if not _args: @@ -1072,7 +1070,9 @@ def prompt(self, more): """Show the appropriate Python prompt""" if not more: self.echo( - "\x01{}\x03{}".format(self.config.color_scheme["prompt"], self.ps1) + "\x01{}\x03{}".format( + self.config.color_scheme["prompt"], self.ps1 + ) ) self.stdout_hist += self.ps1 self.screen_hist.append( diff --git a/bpython/config.py b/bpython/config.py index 066ac7085..643aca76a 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -117,8 +117,14 @@ def loadini(struct, configfile): "up_one_line": "C-p", "yank_from_buffer": "C-y", }, - "cli": {"suggestion_width": 0.8, "trim_prompts": False,}, - "curtsies": {"list_above": False, "right_arrow_completion": True,}, + "cli": { + "suggestion_width": 0.8, + "trim_prompts": False, + }, + "curtsies": { + "list_above": False, + "right_arrow_completion": True, + }, } default_keys_to_commands = { @@ -269,9 +275,7 @@ def get_key_no_doublebind(command): try: load_theme(struct, path, struct.color_scheme, default_colors) except OSError: - sys.stderr.write( - f"Could not load theme '{color_scheme_name}'.\n" - ) + sys.stderr.write(f"Could not load theme '{color_scheme_name}'.\n") sys.exit(1) # expand path of history file diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 763f0ae28..dd74fd13a 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -48,8 +48,8 @@ def __init__(self, config, locals_, banner, interp=None): self._request_reload = self.input_generator.threadsafe_event_trigger( bpythonevents.ReloadEvent ) - self.interrupting_refresh = self.input_generator.threadsafe_event_trigger( - lambda: None + self.interrupting_refresh = ( + self.input_generator.threadsafe_event_trigger(lambda: None) ) self.request_undo = self.input_generator.event_trigger( bpythonevents.UndoEvent diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index a77107e10..d2efe77bb 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -37,9 +37,7 @@ def add(self, key, func, overwrite=False): else: raise ValueError(f"key {key!r} already has a mapping") params = getargspec(func) - args = { - k: v for k, v in self.default_kwargs.items() if k in params - } + args = {k: v for k, v in self.default_kwargs.items() if k in params} r = func(**args) if len(r) == 2: if hasattr(func, "kills"): @@ -56,9 +54,7 @@ def add(self, key, func, overwrite=False): ) self.cut_buffer_edits[key] = func else: - raise ValueError( - f"return type of function {func!r} not recognized" - ) + raise ValueError(f"return type of function {func!r} not recognized") def add_config_attr(self, config_attr, func): if config_attr in self.awaiting_config: diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index ebfb87ac6..7c30143be 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -40,7 +40,11 @@ from bpython.curtsiesfrontend import replpainter as paint from bpython.curtsiesfrontend import sitefix -from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput, is_main_thread +from bpython.curtsiesfrontend.coderunner import ( + CodeRunner, + FakeOutput, + is_main_thread, +) from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler from bpython.curtsiesfrontend.interaction import StatusBar from bpython.curtsiesfrontend.manual_readline import edit_keys @@ -971,7 +975,7 @@ def process_simple_keypress(self, e): self.add_normal_character(e) def send_current_block_to_external_editor(self, filename=None): - """" + """ Sends the current code block to external editor to be edited. Usually bound to C-x. """ text = self.send_to_external_editor(self.get_current_block()) @@ -987,7 +991,7 @@ def send_current_block_to_external_editor(self, filename=None): def send_session_to_external_editor(self, filename=None): """ - Sends entire bpython session to external editor to be edited. Usually bound to F7. + Sends entire bpython session to external editor to be edited. Usually bound to F7. """ for_editor = EDIT_SESSION_HEADER for_editor += self.get_session_formatted_for_file() @@ -1349,16 +1353,11 @@ def display_line_with_prompt(self): more = func_for_letter(self.config.color_scheme["prompt_more"]) if self.incr_search_mode == "reverse_incremental_search": return ( - prompt( - f"(reverse-i-search)`{self.incr_search_target}': " - ) + prompt(f"(reverse-i-search)`{self.incr_search_target}': ") + self.current_line_formatted ) elif self.incr_search_mode == "incremental_search": - return ( - prompt(f"(i-search)`%s': ") - + self.current_line_formatted - ) + return prompt(f"(i-search)`%s': ") + self.current_line_formatted return ( prompt(self.ps1) if self.done else more(self.ps2) ) + self.current_line_formatted @@ -1407,11 +1406,11 @@ def current_output_line(self, value): self.stdin.current_line = "\n" def number_of_padding_chars_on_current_cursor_line(self): - """ To avoid cutting off two-column characters at the end of lines where + """To avoid cutting off two-column characters at the end of lines where there's only one column left, curtsies adds a padding char (u' '). It's important to know about these for cursor positioning. - Should return zero unless there are fullwidth characters. """ + Should return zero unless there are fullwidth characters.""" full_line = self.current_cursor_line_without_suggestion line_with_padding = "".join( line.s diff --git a/bpython/curtsiesfrontend/sitefix.py b/bpython/curtsiesfrontend/sitefix.py index 5d33920cb..96626c16c 100644 --- a/bpython/curtsiesfrontend/sitefix.py +++ b/bpython/curtsiesfrontend/sitefix.py @@ -3,9 +3,7 @@ def resetquit(builtins): - """Redefine builtins 'quit' and 'exit' not so close stdin - - """ + """Redefine builtins 'quit' and 'exit' not so close stdin""" def __call__(self, code=None): raise SystemExit(code) diff --git a/bpython/filelock.py b/bpython/filelock.py index e8217c95d..72018f832 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -39,8 +39,7 @@ class BaseLock: - """Base class for file locking - """ + """Base class for file locking""" def __init__(self, fileobj, mode=None, filename=None): self.fileobj = fileobj @@ -66,8 +65,7 @@ def __del__(self): class UnixFileLock(BaseLock): - """Simple file locking for Unix using fcntl - """ + """Simple file locking for Unix using fcntl""" def __init__(self, fileobj, mode=None, filename=None): super().__init__(fileobj) @@ -90,8 +88,7 @@ def release(self): class WindowsFileLock(BaseLock): - """Simple file locking for Windows using msvcrt - """ + """Simple file locking for Windows using msvcrt""" def __init__(self, fileobj, mode=None, filename=None): super().__init__(None) diff --git a/bpython/history.py b/bpython/history.py index 61c476e85..683a062f6 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -169,9 +169,7 @@ def reset(self): self.saved_line = "" def load(self, filename, encoding): - with open( - filename, encoding=encoding, errors="ignore" - ) as hfile: + with open(filename, encoding=encoding, errors="ignore") as hfile: with FileLock(hfile, filename=filename): self.entries = self.load_from(hfile) diff --git a/bpython/pager.py b/bpython/pager.py index 13f8c3234..e481e7936 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -29,6 +29,7 @@ import sys import shlex + def get_pager_command(default="less -rf"): command = shlex.split(os.environ.get("PAGER", default)) return command diff --git a/bpython/repl.py b/bpython/repl.py index e62ec677c..34d224cf7 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -356,8 +356,8 @@ class SourceNotFound(Exception): class LineTypeTranslator: - """ Used when adding a tuple to all_logical_lines, to get input / output values - having to actually type/know the strings """ + """Used when adding a tuple to all_logical_lines, to get input / output values + having to actually type/know the strings""" # TODO use Enum once we drop support for Python 2 @@ -455,7 +455,8 @@ def __init__(self, interp, config): self.paster = PasteHelper(self.config.pastebin_helper) else: self.paster = PastePinnwand( - self.config.pastebin_url, self.config.pastebin_expiry, + self.config.pastebin_url, + self.config.pastebin_expiry, ) @property @@ -789,7 +790,7 @@ def get_session_formatted_for_file(self): i.e. without >>> and ... at input lines and with "# OUT: " prepended to output lines and "### " prepended to current line""" - if hasattr(self, 'all_logical_lines'): + if hasattr(self, "all_logical_lines"): # Curtsies def process(): @@ -807,13 +808,14 @@ def process(): def process(): for line in session_output.split("\n"): - if line.startswith(self.ps1): - yield line[len(self.ps1) :] - elif line.startswith(self.ps2): - yield line[len(self.ps2) :] - elif line.rstrip(): + if line.startswith(self.ps1): + yield line[len(self.ps1) :] + elif line.startswith(self.ps2): + yield line[len(self.ps2) :] + elif line.rstrip(): yield f"# OUT: {line}" - return '\n'.join(process()) + + return "\n".join(process()) def write2file(self): """Prompt for a filename and write the current contents of the stdout diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 098f93ee1..229e3423d 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -99,8 +99,7 @@ def _convert(node): return list(map(_convert, node.elts)) elif isinstance(node, ast.Dict): return { - _convert(k): _convert(v) - for k, v in zip(node.keys, node.values) + _convert(k): _convert(v) for k, v in zip(node.keys, node.values) } elif isinstance(node, ast.Set): return set(map(_convert, node.elts)) @@ -254,6 +253,4 @@ def evaluate_current_attribute(cursor_offset, line, namespace=None): try: return getattr(obj, attr.word) except AttributeError: - raise EvaluationError( - f"can't lookup attribute {attr.word} on {obj!r}" - ) + raise EvaluationError(f"can't lookup attribute {attr.word} on {obj!r}") diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 282435774..bee638d51 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -85,9 +85,7 @@ def test_first_completer_returns_None(self): class TestCumulativeCompleter(unittest.TestCase): - def completer( - self, matches, - ): + def completer(self, matches): mock_completer = autocomplete.BaseCompletionType() mock_completer.matches = mock.Mock(return_value=matches) return mock_completer @@ -274,7 +272,8 @@ def test_custom_get_attribute_not_invoked(self): def test_slots_not_crash(self): com = autocomplete.AttrCompletion() self.assertSetEqual( - com.matches(2, "A.", locals_={"A": Slots}), {"A.b", "A.a"}, + com.matches(2, "A.", locals_={"A": Slots}), + {"A.b", "A.a"}, ) @@ -437,9 +436,5 @@ def func(apple, apricot, banana, carrot): self.assertSetEqual( com.matches(1, "a", argspec=argspec), {"apple=", "apricot="} ) - self.assertSetEqual( - com.matches(2, "ba", argspec=argspec), {"banana="} - ) - self.assertSetEqual( - com.matches(3, "car", argspec=argspec), {"carrot="} - ) + self.assertSetEqual(com.matches(2, "ba", argspec=argspec), {"banana="}) + self.assertSetEqual(com.matches(3, "car", argspec=argspec), {"carrot="}) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index e636b201f..868489061 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -531,7 +531,7 @@ def test_fuzzy_tab_complete(self): # Edge Cases def test_normal_tab(self): """make sure pressing the tab key will - still in some cases add a tab""" + still in some cases add a tab""" self.repl.s = "" self.repl.config = mock.Mock() self.repl.config.tab_length = 4 diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 3c4d11b43..2865ed68f 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -20,7 +20,9 @@ def test_matches_stdlib(self): self.assertMatchesStdlib("{(1,): [2,3,{}]}") self.assertMatchesStdlib("{1, 2}") - @unittest.skipUnless(sys.version_info[:2] >= (3, 9), "Only Python3.9 evaluates set()") + @unittest.skipUnless( + sys.version_info[:2] >= (3, 9), "Only Python3.9 evaluates set()" + ) def test_matches_stdlib_set_literal(self): """set() is evaluated""" self.assertMatchesStdlib("set()") diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index e32957b4d..c2e23f806 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -7,12 +7,15 @@ translator = None + def _(message): return translator.gettext(message) + def ngettext(singular, plural, n): return translator.ngettext(singular, plural, n) + def init(locale_dir=None, languages=None): try: locale.setlocale(locale.LC_ALL, "") diff --git a/bpython/urwid.py b/bpython/urwid.py index c22c8fb8a..019a77c7c 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -242,7 +242,7 @@ def prompt(self, s=None, single=False): def settext(self, s, permanent=False): """Set the text on the status bar to a new value. If permanent is True, the new value will be permanent. If that status bar is in prompt mode, - the prompt will be aborted. """ + the prompt will be aborted.""" self._reset_timer() @@ -774,9 +774,7 @@ def _populate_completion(self): in_arg = self.arg_pos args, varargs, varkw, defaults = args[:4] kwonly = self.funcprops.argspec.kwonly - kwonly_defaults = ( - self.funcprops.argspec.kwonly_defaults or {} - ) + kwonly_defaults = self.funcprops.argspec.kwonly_defaults or {} markup = [("bold name", func_name), ("name", ": (")] # the isinstance checks if we're in a positional arg From baf242649cc6cc7b565ab5012e8e93cb1ebf0306 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 21:00:25 +0200 Subject: [PATCH 1062/1650] Update German translation --- bpython/translations/de/LC_MESSAGES/bpython.po | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 1344567f1..7023785e6 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" "POT-Creation-Date: 2020-10-13 21:53+0200\n" -"PO-Revision-Date: 2020-01-06 12:17+0100\n" +"PO-Revision-Date: 2020-10-19 20:59+0200\n" "Last-Translator: Sebastian Ramacher \n" "Language: de\n" "Language-Team: de \n" @@ -84,7 +84,7 @@ msgstr "" #: bpython/curtsies.py:147 msgid "log debug messages to bpython.log" -msgstr "" +msgstr "Zeichne debug Nachrichten in bpython.log auf" #: bpython/curtsies.py:153 msgid "start by pasting lines of a file into session" @@ -118,7 +118,7 @@ msgstr "Konnte Ausgabe von Hilfsprogramm nicht verarbeiten." #: bpython/repl.py:657 msgid "Nothing to get source of" -msgstr "" +msgstr "Nichts um Quellcode abzurufen" #: bpython/repl.py:662 #, python-format @@ -214,6 +214,8 @@ msgstr "" #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" +"Wie viele Zeilen rückgängig machen? (Rückgängigmachen wird bis zu ~%1.f " +"Sekunden brauchen) [1]" #: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" @@ -223,8 +225,8 @@ msgstr "Rückgängigmachen abgebrochen" #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Mache %d Zeile rückgängig... (ungefähr %.1f Sekunden)" +msgstr[1] "Mache %d Zeilen rückgängig... (ungefähr %.1f Sekunden)" #: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" @@ -247,6 +249,8 @@ msgstr "Fehler beim Bearbeiten der Konfigurationsdatei: %s" #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" +" <%s> Rückgängigmachen <%s> Speichern <%s> Pastebin <%s> Pager <%s> " +"Quellcode anzeigen " #: bpython/urwid.py:1136 msgid "Run twisted reactor." From b2445328bdb40ec2c944e17c9ef39c9dff8c5cda Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 22:29:14 +0200 Subject: [PATCH 1063/1650] Revert "Remove Enum to maintain Python 2 support" This reverts commit ac93c749804f953c4c93e3674e23089e5332dc83. --- bpython/repl.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 34d224cf7..5857dc9dd 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -36,6 +36,7 @@ import traceback from itertools import takewhile from types import ModuleType +from enum import Enum from pygments.token import Token from pygments.lexers import Python3Lexer @@ -355,12 +356,10 @@ class SourceNotFound(Exception): """Exception raised when the requested source could not be found.""" -class LineTypeTranslator: +class LineTypeTranslator(Enum): """Used when adding a tuple to all_logical_lines, to get input / output values having to actually type/know the strings""" - # TODO use Enum once we drop support for Python 2 - INPUT = "input" OUTPUT = "output" From ef5267253b67edd9aad7749e4349172784ef3ab9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 22:50:25 +0200 Subject: [PATCH 1064/1650] Generate black-compatible _version.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c5e37c0f0..ec71c2fdd 100755 --- a/setup.py +++ b/setup.py @@ -119,7 +119,7 @@ def git_describe_to_python_version(version): with open(version_file, "w") as vf: vf.write("# Auto-generated file, do not edit!\n") - vf.write(f"__version__ = '{version}'\n") + vf.write(f"__version__ = \"{version}\"\n") class install(_install): From 093d8445ab2617cf6b31ea191eead8135716be16 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 23:30:34 +0200 Subject: [PATCH 1065/1650] Provide more meta data in bpython and bpdb modules --- bpdb/__init__.py | 5 ++++- bpython/__init__.py | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index 7c2be1717..a6cea15c0 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -1,7 +1,7 @@ # The MIT License # # Copyright (c) 2008 Bob Farrell -# Copyright (c) 2013 Sebastian Ramacher +# Copyright (c) 2013-2020 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -30,6 +30,9 @@ from optparse import OptionParser from pdb import Restart +__author__ = bpython.__author__ +__copyright__ = bpython.__copyright__ +__license__ = bpython.__license__ __version__ = bpython.__version__ diff --git a/bpython/__init__.py b/bpython/__init__.py index 04e84e7b3..f9048afa8 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -20,7 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - import os.path try: @@ -28,6 +27,11 @@ except ImportError: version = "unknown" +__author__ = ( + "Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al." +) +__copyright__ = f"(C) 2008-2020 {__author__}" +__license__ = "MIT" __version__ = version package_dir = os.path.abspath(os.path.dirname(__file__)) From ff122520bf08bae097cb171cae99df426536391d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 23:32:09 +0200 Subject: [PATCH 1066/1650] Refactor copyright banner --- bpdb/__init__.py | 9 +++------ bpython/args.py | 40 ++++++++++++++++++++++++++++++++-------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index a6cea15c0..ccb3a4d91 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -26,6 +26,7 @@ import traceback import bpython +from bpython.args import version_banner, copyright_banner from .debugger import BPdb from optparse import OptionParser from pdb import Restart @@ -74,12 +75,8 @@ def main(): ) options, args = parser.parse_args(sys.argv) if options.version: - print("bpdb on top of bpython version", __version__, end="") - print("on top of Python", sys.version.split()[0]) - print( - "(C) 2008-2013 Bob Farrell, Andreas Stuehrk et al. " - "See AUTHORS for detail." - ) + print(version_banner(base="bpdb")) + print(copyright_banner()) return 0 if len(args) < 2: diff --git a/bpython/args.py b/bpython/args.py index 6eef37759..479590ffc 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -1,15 +1,37 @@ +# The MIT License +# +# Copyright (c) 2008 Bob Farrell +# Copyright (c) 2012-2020 Sebastian Ramacher +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + """ Module to handle command line argument parsing, for all front-ends. """ - import code import importlib.util import os import sys from optparse import OptionParser, OptionGroup -from . import __version__ +from . import __version__, __copyright__ from .config import default_config_path, loadini, Struct from .translations import _ @@ -23,14 +45,19 @@ def error(self, msg): raise OptionParserFailed() -def version_banner(): - return "bpython version {} on top of Python {} {}".format( +def version_banner(base="bpython"): + return "{} version {} on top of Python {} {}".format( + base, __version__, sys.version.split()[0], sys.executable, ) +def copyright_banner(): + return "{} See AUTHORS for details.".format(__copyright__) + + def parse(args, extras=None, ignore_stdin=False): """Receive an argument list - if None, use sys.argv - parse all args and take appropriate action. Also receive optional extra options: this should @@ -110,10 +137,7 @@ def parse(args, extras=None, ignore_stdin=False): if options.version: print(version_banner()) - print( - "(C) 2008-2020 Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al. " - "See AUTHORS for detail." - ) + print(copyright_banner()) raise SystemExit if not ignore_stdin and not (sys.stdin.isatty() and sys.stdout.isatty()): From 8c9f37ce561187f35829864421eb14e14f418df3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 23:37:44 +0200 Subject: [PATCH 1067/1650] Update user facing strings --- bpdb/__init__.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/bpdb/__init__.py b/bpdb/__init__.py index ccb3a4d91..9ce932d38 100644 --- a/bpdb/__init__.py +++ b/bpdb/__init__.py @@ -55,8 +55,7 @@ def post_mortem(t=None): t = sys.exc_info()[2] if t is None: raise ValueError( - "A valid traceback must be passed if no " - "exception is being handled" + "A valid traceback must be passed if no exception is being handled." ) p = BPdb() @@ -86,7 +85,7 @@ def main(): # The following code is based on Python's pdb.py. mainpyfile = args[1] if not os.path.exists(mainpyfile): - print("Error:", mainpyfile, "does not exist") + print(f"Error: {mainpyfile} does not exist.") return 1 # Hide bpdb from argument list. @@ -101,22 +100,22 @@ def main(): pdb._runscript(mainpyfile) if pdb._user_requested_quit: break - print("The program finished and will be restarted") + print("The program finished and will be restarted.") except Restart: - print("Restarting", mainpyfile, "with arguments:") + print(f"Restarting {mainpyfile} with arguments:") print("\t" + " ".join(sys.argv[1:])) except SystemExit: # In most cases SystemExit does not warrant a post-mortem session. - print("The program exited via sys.exit(). Exit status: ",) + print( + "The program exited via sys.exit(). Exit status: ", + ) print(sys.exc_info()[1]) except: traceback.print_exc() - print("Uncaught exception. Entering post mortem debugging") - print("Running 'cont' or 'step' will restart the program") + print("Uncaught exception. Entering post mortem debugging.") + print("Running 'cont' or 'step' will restart the program.") t = sys.exc_info()[2] pdb.interaction(None, t) print( - "Post mortem debugger finished. The " - + mainpyfile - + " will be restarted" + f"Post mortem debugger finished. The {mainpyfile} will be restarted." ) From ea44aec13ef33cc14e52f7bfaa4d7086d0ab7179 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 19 Oct 2020 23:48:04 +0200 Subject: [PATCH 1068/1650] Update doc string --- bpython/simpleeval.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 229e3423d..2b414ee25 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -74,10 +74,10 @@ def simple_eval(node_or_string, namespace=None): * variable names causing lookups in the passed in namespace or builtins * getitem calls using the [] syntax on objects of the types above - Like the Python 3 (and unlike the Python 2) literal_eval, unary and binary - + and - operations are allowed on all builtin numeric types. + Like Python 3's literal_eval, unary and binary + and - operations are + allowed on all builtin numeric types. - The optional namespace dict-like ought not to cause side effects on lookup + The optional namespace dict-like ought not to cause side effects on lookup. """ if namespace is None: namespace = {} From be8737c1dd507439d78c3f9b2c41967e544343e1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Oct 2020 00:58:35 +0200 Subject: [PATCH 1069/1650] Use tuples instead of lists --- bpython/cli.py | 2 +- bpython/curtsiesfrontend/interaction.py | 2 +- bpython/curtsiesfrontend/repl.py | 23 +++++++++++------------ bpython/lazyre.py | 1 - bpython/repl.py | 8 ++------ bpython/urwid.py | 4 ++-- 6 files changed, 17 insertions(+), 23 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index cd9a20904..4cedab8d8 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -190,7 +190,7 @@ def readline(self, size=-1): try: while not buffer.endswith(("\n", "\r")): key = self.interface.get_key() - if key in [curses.erasechar(), "KEY_BACKSPACE"]: + if key in (curses.erasechar(), "KEY_BACKSPACE"): y, x = self.interface.scr.getyx() if buffer: self.interface.scr.delch(y, x - 1) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index abd6009c6..7820c7c87 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -87,7 +87,7 @@ def process_event(self, e): for ee in e.events: # strip control seq self.add_normal_character(ee if len(ee) == 1 else ee[-1]) - elif e in [""] or isinstance(e, events.SigIntEvent): + elif e == "" or isinstance(e, events.SigIntEvent): self.request_context.switch(False) self.escape() elif e in edit_keys: diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 7c30143be..32ddd8362 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -136,10 +136,9 @@ def process_event(self, e): self.repl.run_code_and_maybe_finish() elif e in ("",): self.get_last_word() - - elif e in [""]: + elif e in ("",): pass - elif e in [""]: + elif e in ("",): if self.current_line == "": self.repl.send_to_stdin("\n") self.has_focus = False @@ -148,7 +147,7 @@ def process_event(self, e): self.repl.run_code_and_maybe_finish(for_code="") else: pass - elif e in ["\n", "\r", "", ""]: + elif e in ("\n", "\r", "", ""): line = self.current_line self.repl.send_to_stdin(line + "\n") self.has_focus = False @@ -164,7 +163,7 @@ def process_event(self, e): self.repl.send_to_stdin(self.current_line) def add_input_character(self, e): - if e == "": + if e in ("",): e = " " if e.startswith("<") and e.endswith(">"): return @@ -763,7 +762,7 @@ def process_key_event(self, e): raise SystemExit() elif e in ("\n", "\r", "", "", ""): self.on_enter() - elif e == "": # tab + elif e in ("",): # tab self.on_tab() elif e in ("",): self.on_tab(back=True) @@ -784,9 +783,9 @@ def process_key_event(self, e): # TODO add PAD keys hack as in bpython.cli elif e in key_dispatch[self.config.edit_current_block_key]: self.send_current_block_to_external_editor() - elif e in [""]: + elif e in ("",): self.incr_search_mode = None - elif e in [""]: + elif e in ("",): self.add_normal_character(" ") else: self.add_normal_character(e) @@ -969,7 +968,7 @@ def process_simple_keypress(self, e): self.process_event(bpythonevents.RefreshRequestEvent()) elif isinstance(e, events.Event): pass # ignore events - elif e == "": + elif e in ("",): self.add_normal_character(" ") else: self.add_normal_character(e) @@ -2013,11 +2012,11 @@ def key_help_text(self): ["complete history suggestion", "right arrow at end of line"] ) pairs.append(["previous match with current line", "up arrow"]) - for functionality, key in [ + for functionality, key in ( (attr[:-4].replace("_", " "), getattr(self.config, attr)) for attr in self.config.__dict__ if attr.endswith("key") - ]: + ): if functionality in NOT_IMPLEMENTED: key = "Not Implemented" if key == "": @@ -2070,7 +2069,7 @@ def just_simple_events(event_list): simple_events.append("\n") elif isinstance(e, events.Event): pass # ignore events - elif e == "": + elif e in ("",): simple_events.append(" ") elif len(e) > 1: pass # get rid of etc. diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 588558b85..01747f819 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -20,7 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - import re diff --git a/bpython/repl.py b/bpython/repl.py index 5857dc9dd..9b23221ad 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -21,7 +21,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - import code import inspect import os @@ -40,9 +39,7 @@ from pygments.token import Token from pygments.lexers import Python3Lexer - -from . import autocomplete -from . import inspection +from . import autocomplete, inspection, simpleeval from .clipboard import get_clipboard, CopyFailed from .config import getpreferredencoding from .formatter import Parenthesis @@ -51,7 +48,6 @@ from .paste import PasteHelper, PastePinnwand, PasteFailed from .patch_linecache import filename_for_console_input from .translations import _, ngettext -from . import simpleeval class RuntimeTimer: @@ -1206,7 +1202,7 @@ def next_token_inside_string(code_string, inside_string): for token, value in Python3Lexer().get_tokens(code_string): if token is Token.String: value = value.lstrip("bBrRuU") - if value in ['"""', "'''", '"', "'"]: + if value in ('"""', "'''", '"', "'"): if not inside_string: inside_string = value elif value == inside_string: diff --git a/bpython/urwid.py b/bpython/urwid.py index 019a77c7c..27f1ef116 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -393,7 +393,7 @@ def move_cursor_to_coords(self, *args): return False def keypress(self, size, key): - if urwid.command_map[key] in ["cursor up", "cursor down"]: + if urwid.command_map[key] in ("cursor up", "cursor down"): # Do not handle up/down arrow, leave them for the repl. return key @@ -452,7 +452,7 @@ class BPythonListBox(urwid.ListBox): """ def keypress(self, size, key): - if key not in ["up", "down"]: + if key not in ("up", "down"): return urwid.ListBox.keypress(self, size, key) return key From 4e11d5ed6f4df3faab5446c4300f98cd564c8d9a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 21 Oct 2020 01:03:58 +0200 Subject: [PATCH 1070/1650] Remove __future__ imports --- bpython/test/test_curtsies_repl.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index b1eb38997..de5fdb96c 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -244,7 +244,6 @@ class TestFutureImports(TestCase): def test_repl(self): repl = create_repl() with captured_output() as (out, err): - repl.push("from __future__ import division") repl.push("1 / 2") self.assertEqual(out.getvalue(), "0.5\n") @@ -252,7 +251,6 @@ def test_interactive(self): interp = code.InteractiveInterpreter(locals={}) with captured_output() as (out, err): with tempfile.NamedTemporaryFile(mode="w", suffix=".py") as f: - f.write("from __future__ import division\n") f.write("print(1/2)\n") f.flush() args.exec_code(interp, [f.name]) @@ -438,7 +436,6 @@ def write_startup_file(self, fname, encoding): f.write("# coding: ") f.write(encoding) f.write("\n") - f.write("from __future__ import unicode_literals\n") f.write('a = "äöü"\n') def test_startup_event_utf8(self): From 7c58ca03143a6d05429e5e98d44e4497cbf61cf7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 24 Oct 2020 17:46:33 +0200 Subject: [PATCH 1071/1650] Bump Sphinx version requirement --- .travis.install.sh | 2 +- README.rst | 2 +- setup.py | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.install.sh b/.travis.install.sh index 43392c336..58a021274 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -17,5 +17,5 @@ if [[ $RUN == nosetests ]]; then python setup.py install elif [[ $RUN == build_sphinx ]]; then # documentation specific dependencies - pip install 'sphinx >=1.1.3' + pip install 'sphinx >=1.5' fi diff --git a/README.rst b/README.rst index 88924e7da..ad9000781 100644 --- a/README.rst +++ b/README.rst @@ -99,7 +99,7 @@ Dependencies * requests * curtsies >= 0.3.0 * greenlet -* Sphinx != 1.1.2 (optional, for the documentation) +* Sphinx >= 1.5 (optional, for the documentation) * babel (optional, for internationalization) * watchdog (optional, for monitoring imported modules for changes) * jedi (optional, for experimental multiline completion) diff --git a/setup.py b/setup.py index ec71c2fdd..6a10f8296 100755 --- a/setup.py +++ b/setup.py @@ -24,9 +24,8 @@ import sphinx from sphinx.setup_command import BuildDoc - # Sphinx 1.1.2 is buggy and building bpython with that version fails. - # See #241. - using_sphinx = sphinx.__version__ >= "1.1.3" + # Sphinx 1.5 and newer support Python 3.6 + using_sphinx = sphinx.__version__ >= "1.5" except ImportError: using_sphinx = False From d55c640f05979671dfedeb03ac1bf7a6e04d75d1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 24 Oct 2020 22:32:36 +0200 Subject: [PATCH 1072/1650] Fix version parsing --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6a10f8296..d31cbaeea 100755 --- a/setup.py +++ b/setup.py @@ -99,7 +99,7 @@ def git_describe_to_python_version(version): try: # get version from existing version file with open(version_file) as vf: - version = vf.read().strip().split("=")[-1].replace("'", "") + version = vf.read().strip().split("=")[-1].replace("'", "").replace("\"", "") version = version.strip() except OSError: pass From 4270a7b7fc6155728a0e99a06193bf10301e268d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Oct 2020 15:31:52 +0100 Subject: [PATCH 1073/1650] Remove unused import --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index d31cbaeea..33913d2d1 100755 --- a/setup.py +++ b/setup.py @@ -4,7 +4,6 @@ import platform import re import subprocess -import sys from distutils.command.build import build from setuptools import setup From 71c419d5bb33e1dc518bf228f1c8645eb427e054 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Oct 2020 16:00:31 +0100 Subject: [PATCH 1074/1650] Replace list of options with a callback This will make it easier to later change to argparse. --- bpython/args.py | 13 ++++---- bpython/curtsies.py | 30 +++++++++---------- bpython/urwid.py | 72 ++++++++++++++++++++++----------------------- 3 files changed, 58 insertions(+), 57 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 479590ffc..586c73bda 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -64,17 +64,19 @@ def parse(args, extras=None, ignore_stdin=False): be a tuple of (title, description, options) title: The title for the option group description: A full description of the option group - options: A list of optparse.Option objects to be added to the - group + callback: A callback that adds options to the option group e.g.: + def callback(group): + group.add_option('-f', action='store_true', dest='f', help='Explode') + group.add_option('-l', action='store_true', dest='l', help='Love') + parse( ['-i', '-m', 'foo.py'], ('Front end-specific options', 'A full description of what these options are for', - [optparse.Option('-f', action='store_true', dest='f', help='Explode'), - optparse.Option('-l', action='store_true', dest='l', help='Love')])) + callback)) Return a tuple of (config, options, exec_args) wherein "config" is the @@ -125,8 +127,7 @@ def parse(args, extras=None, ignore_stdin=False): if extras is not None: extras_group = OptionGroup(parser, extras[0], extras[1]) - for option in extras[2]: - extras_group.add_option(option) + extras[2](extras_group) parser.add_option_group(extras_group) try: diff --git a/bpython/curtsies.py b/bpython/curtsies.py index dd74fd13a..94676edb8 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -1,7 +1,6 @@ import collections import logging import sys -from optparse import Option import curtsies import curtsies.window @@ -134,25 +133,26 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): """ translations.init() + def curtsies_arguments(parser): + parser.add_argument( + "--log", + "-L", + action="count", + help=_("log debug messages to bpython.log"), + ) + parser.add_argument( + "--paste", + "-p", + action="store_true", + help=_("start by pasting lines of a file into session"), + ) + config, options, exec_args = bpargs.parse( args, ( "curtsies options", None, - [ - Option( - "--log", - "-L", - action="count", - help=_("log debug messages to bpython.log"), - ), - Option( - "--paste", - "-p", - action="store_true", - help=_("start by pasting lines of a file into session"), - ), - ], + curtsies_arguments, ), ) if options.log is None: diff --git a/bpython/urwid.py b/bpython/urwid.py index 27f1ef116..0e2570e88 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -37,7 +37,6 @@ import time import locale import signal -from optparse import Option from pygments.token import Token @@ -1120,47 +1119,48 @@ def tab(self, back=False): def main(args=None, locals_=None, banner=None): translations.init() + def options_callback(group): + group.add_option( + "--twisted", + "-T", + action="store_true", + help=_("Run twisted reactor."), + ) + group.add_option( + "--reactor", + "-r", + help=_( + "Select specific reactor (see --help-reactors). " + "Implies --twisted." + ), + ) + group.add_option( + "--help-reactors", + action="store_true", + help=_("List available reactors for -r."), + ) + group.add_option( + "--plugin", + "-p", + help=_( + "twistd plugin to run (use twistd for a list). " + 'Use "--" to pass further options to the plugin.' + ), + ) + group.add_option( + "--server", + "-s", + type="int", + help=_("Port to run an eval server on (forces Twisted)."), + ) + # TODO: maybe support displays other than raw_display? config, options, exec_args = bpargs.parse( args, ( "Urwid options", None, - [ - Option( - "--twisted", - "-T", - action="store_true", - help=_("Run twisted reactor."), - ), - Option( - "--reactor", - "-r", - help=_( - "Select specific reactor (see --help-reactors). " - "Implies --twisted." - ), - ), - Option( - "--help-reactors", - action="store_true", - help=_("List available reactors for -r."), - ), - Option( - "--plugin", - "-p", - help=_( - "twistd plugin to run (use twistd for a list). " - 'Use "--" to pass further options to the plugin.' - ), - ), - Option( - "--server", - "-s", - type="int", - help=_("Port to run an eval server on (forces Twisted)."), - ), - ], + options_callback, ), ) From 0327893bb397c36b129a5bbd2f162461920ce251 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Oct 2020 16:08:40 +0100 Subject: [PATCH 1075/1650] Fix add_option calls --- bpython/curtsies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 94676edb8..b80a52ce3 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -134,13 +134,13 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): translations.init() def curtsies_arguments(parser): - parser.add_argument( + parser.add_option( "--log", "-L", action="count", help=_("log debug messages to bpython.log"), ) - parser.add_argument( + parser.add_option( "--paste", "-p", action="store_true", From ce29d8b391bf6083448ea834e13638de685ea44d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Oct 2020 16:18:08 +0100 Subject: [PATCH 1076/1650] Replace deprecated optparse with argparse --- bpython/args.py | 51 +++++++++++++++++++++------------------------ bpython/curtsies.py | 4 ++-- bpython/urwid.py | 12 +++++------ 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 586c73bda..6e64901d7 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -29,20 +29,20 @@ import importlib.util import os import sys -from optparse import OptionParser, OptionGroup +import argparse from . import __version__, __copyright__ from .config import default_config_path, loadini, Struct from .translations import _ -class OptionParserFailed(ValueError): +class ArgumentParserFailed(ValueError): """Raised by the RaisingOptionParser for a bogus commandline.""" -class RaisingOptionParser(OptionParser): +class RaisingArgumentParser(argparse.ArgumentParser): def error(self, msg): - raise OptionParserFailed() + raise ArgumentParserFailed() def version_banner(base="bpython"): @@ -60,17 +60,17 @@ def copyright_banner(): def parse(args, extras=None, ignore_stdin=False): """Receive an argument list - if None, use sys.argv - parse all args and - take appropriate action. Also receive optional extra options: this should - be a tuple of (title, description, options) - title: The title for the option group - description: A full description of the option group - callback: A callback that adds options to the option group + take appropriate action. Also receive optional extra argument: this should + be a tuple of (title, description, callback) + title: The title for the argument group + description: A full description of the argument group + callback: A callback that adds argument to the argument group e.g.: def callback(group): - group.add_option('-f', action='store_true', dest='f', help='Explode') - group.add_option('-l', action='store_true', dest='l', help='Love') + group.add_argument('-f', action='store_true', dest='f', help='Explode') + group.add_argument('-l', action='store_true', dest='l', help='Love') parse( ['-i', '-m', 'foo.py'], @@ -82,43 +82,38 @@ def callback(group): Return a tuple of (config, options, exec_args) wherein "config" is the config object either parsed from a default/specified config file or default config options, "options" is the parsed options from - OptionParser.parse_args, and "exec_args" are the args (if any) to be parsed + ArgumentParser.parse_args, and "exec_args" are the args (if any) to be parsed to the executed file (if any). """ if args is None: args = sys.argv[1:] - parser = RaisingOptionParser( + parser = RaisingArgumentParser( usage=_( - "Usage: %prog [options] [file [args]]\n" + "Usage: %(prog)s [options] [file [args]]\n" "NOTE: If bpython sees an argument it does " "not know, execution falls back to the " "regular Python interpreter." ) ) - # This is not sufficient if bpython gains its own -m support - # (instead of falling back to Python itself for that). - # That's probably fixable though, for example by having that - # option swallow all remaining arguments in a callback. - parser.disable_interspersed_args() - parser.add_option( + parser.add_argument( "--config", default=default_config_path(), help=_("Use CONFIG instead of default config file."), ) - parser.add_option( + parser.add_argument( "--interactive", "-i", action="store_true", help=_("Drop to bpython shell after running file instead of exiting."), ) - parser.add_option( + parser.add_argument( "--quiet", "-q", action="store_true", help=_("Don't flush the output to stdout."), ) - parser.add_option( + parser.add_argument( "--version", "-V", action="store_true", @@ -126,12 +121,14 @@ def callback(group): ) if extras is not None: - extras_group = OptionGroup(parser, extras[0], extras[1]) + extras_group = parser.add_argument_group(extras[0], extras[1]) extras[2](extras_group) - parser.add_option_group(extras_group) + + # collect all the remaining arguments into a list + parser.add_argument('args', nargs=argparse.REMAINDER) try: - options, args = parser.parse_args(args) + options = parser.parse_args(args) except OptionParserFailed: # Just let Python handle this os.execv(sys.executable, [sys.executable] + args) @@ -149,7 +146,7 @@ def callback(group): config = Struct() loadini(config, options.config) - return config, options, args + return config, options, options.args def exec_code(interpreter, args): diff --git a/bpython/curtsies.py b/bpython/curtsies.py index b80a52ce3..94676edb8 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -134,13 +134,13 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): translations.init() def curtsies_arguments(parser): - parser.add_option( + parser.add_argument( "--log", "-L", action="count", help=_("log debug messages to bpython.log"), ) - parser.add_option( + parser.add_argument( "--paste", "-p", action="store_true", diff --git a/bpython/urwid.py b/bpython/urwid.py index 0e2570e88..e3e14d37e 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1120,13 +1120,13 @@ def main(args=None, locals_=None, banner=None): translations.init() def options_callback(group): - group.add_option( + group.add_argument( "--twisted", "-T", action="store_true", help=_("Run twisted reactor."), ) - group.add_option( + group.add_argument( "--reactor", "-r", help=_( @@ -1134,12 +1134,12 @@ def options_callback(group): "Implies --twisted." ), ) - group.add_option( + group.add_argument( "--help-reactors", action="store_true", help=_("List available reactors for -r."), ) - group.add_option( + group.add_argument( "--plugin", "-p", help=_( @@ -1147,10 +1147,10 @@ def options_callback(group): 'Use "--" to pass further options to the plugin.' ), ) - group.add_option( + group.add_argument( "--server", "-s", - type="int", + type=int, help=_("Port to run an eval server on (forces Twisted)."), ) From d10e33593b8a5f3c42e56f3ac764d3cb7412e733 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Oct 2020 11:31:04 +0100 Subject: [PATCH 1077/1650] Remove unused import --- bpython/simpleeval.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 2b414ee25..37562c933 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -28,7 +28,6 @@ import ast -import inspect import sys import builtins From 5617a438c01adeca9eebacb0f76baaaff0336d67 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Oct 2020 11:34:01 +0100 Subject: [PATCH 1078/1650] Use isinstance for type checking --- bpython/simpleeval.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 37562c933..ca8f5d51b 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -139,7 +139,8 @@ def _convert(node): left = _convert(node.left) right = _convert(node.right) if not ( - type(left) in _numeric_types and type(right) in _numeric_types + isinstance(left, _numeric_types) + and isinstance(right, _numeric_types) ): raise ValueError("binary + and - only allowed on builtin nums") if isinstance(node.op, ast.Add): From cf6c550362f7a47da546f00995f0c63423770b79 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Oct 2020 11:42:44 +0100 Subject: [PATCH 1079/1650] Remove unused imports --- bpython/curtsiesfrontend/_internal.py | 2 -- bpython/curtsiesfrontend/coderunner.py | 4 +--- bpython/paste.py | 9 ++++----- bpython/urwid.py | 6 +----- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index b52a47542..d5e8cb458 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -21,9 +21,7 @@ # THE SOFTWARE. import pydoc - import bpython._internal -from bpython.repl import getpreferredencoding class NopPydocPager: diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index ee62e7c6e..8baabf35c 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -11,13 +11,11 @@ """ import code -import signal import greenlet import logging +import signal import threading -from bpython.config import getpreferredencoding - logger = logging.getLogger(__name__) diff --git a/bpython/paste.py b/bpython/paste.py index 736adea3f..ef27e0295 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -1,6 +1,6 @@ # The MIT License # -# Copyright (c) 2014-2015 Sebastian Ramacher +# Copyright (c) 2014-2020 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -20,15 +20,14 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - -from locale import getpreferredencoding -from urllib.parse import quote as urlquote, urljoin, urlparse -from string import Template import errno import requests import subprocess import unicodedata +from locale import getpreferredencoding +from urllib.parse import urljoin, urlparse + from .translations import _ diff --git a/bpython/urwid.py b/bpython/urwid.py index e3e14d37e..cc7d22f56 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -31,25 +31,21 @@ This is still *VERY* rough. """ -import builtins import sys import os import time import locale import signal +import urwid from pygments.token import Token from . import args as bpargs, repl, translations -from .config import getpreferredencoding from .formatter import theme_map from .importcompletion import find_coroutine from .translations import _ - from .keys import urwid_key_dispatch as key_dispatch -import urwid - Parenthesis = Token.Punctuation.Parenthesis # Urwid colors are: From fb372e37f1e4526d76ef988a561f12b21e0efcdd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Oct 2020 11:42:53 +0100 Subject: [PATCH 1080/1650] Refactor --- bpython/paste.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bpython/paste.py b/bpython/paste.py index ef27e0295..4d118cfc2 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -96,16 +96,16 @@ def paste(self, s): if not paste_url: raise PasteFailed(_("No output from helper program.")) - else: - parsed_url = urlparse(paste_url) - if not parsed_url.scheme or any( - unicodedata.category(c) == "Cc" for c in paste_url - ): - raise PasteFailed( - _( - "Failed to recognize the helper " - "program's output as an URL." - ) + + parsed_url = urlparse(paste_url) + if not parsed_url.scheme or any( + unicodedata.category(c) == "Cc" for c in paste_url + ): + raise PasteFailed( + _( + "Failed to recognize the helper " + "program's output as an URL." ) + ) return paste_url, None From dee4d23c54c015a2e678123658f86e2cdf557d82 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Oct 2020 14:17:51 +0100 Subject: [PATCH 1081/1650] Remove more unused imports --- bpython/importcompletion.py | 11 +++++------ bpython/test/test_autocomplete.py | 9 ++------- bpython/test/test_curtsies_painting.py | 1 - bpython/test/test_import_not_cyclical.py | 1 - bpython/test/test_interpreter.py | 1 - 5 files changed, 7 insertions(+), 16 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 6e30861a2..91972a6b5 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -20,6 +20,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +import fnmatch +import os +import sys +import warnings +import importlib.machinery from .line import ( current_word, @@ -28,12 +33,6 @@ current_from_import_import, ) -import fnmatch -import os -import sys -import warnings -import importlib.machinery - SUFFIXES = importlib.machinery.all_suffixes() # The cached list of all known modules diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index bee638d51..5b7ccd6ca 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -1,12 +1,7 @@ -from collections import namedtuple import inspect import keyword -import sys - -try: - import unittest2 as unittest -except ImportError: - import unittest +import unittest +from collections import namedtuple try: import jedi diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 720b5966e..a5d958a05 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -1,5 +1,4 @@ import itertools -import os import pydoc import string import sys diff --git a/bpython/test/test_import_not_cyclical.py b/bpython/test/test_import_not_cyclical.py index 830a23117..a1001365c 100644 --- a/bpython/test/test_import_not_cyclical.py +++ b/bpython/test/test_import_not_cyclical.py @@ -1,5 +1,4 @@ import os -import sys import tempfile from bpython.test import unittest diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 56d0c8ad7..afa71aa79 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -1,6 +1,5 @@ import sys import re -from textwrap import dedent from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain From 6a1bc38d0201bf3b74a74908f86993017a28cb9e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Oct 2020 14:39:46 +0100 Subject: [PATCH 1082/1650] travis: install urwid and twisted --- .travis.install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.install.sh b/.travis.install.sh index 58a021274..40c0a67bb 100755 --- a/.travis.install.sh +++ b/.travis.install.sh @@ -7,6 +7,8 @@ pip install setuptools if [[ $RUN == nosetests ]]; then # core dependencies pip install -r requirements.txt + # urwid specific dependencies + pip install urwid twisted # filewatch specific dependencies pip install watchdog # jedi specific dependencies From cf81346df833e5a43ff12f4f999a598aaad932e7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Oct 2020 14:59:28 +0100 Subject: [PATCH 1083/1650] Fix type --- bpython/args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/args.py b/bpython/args.py index 6e64901d7..b83844e20 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -129,7 +129,7 @@ def callback(group): try: options = parser.parse_args(args) - except OptionParserFailed: + except ArgumentParserFailed: # Just let Python handle this os.execv(sys.executable, [sys.executable] + args) From 810364fd5103208ea7c21643b6ca206aa488abe3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Oct 2020 15:02:51 +0100 Subject: [PATCH 1084/1650] Improve description of arguments --- bpython/args.py | 8 +++++++- bpython/curtsies.py | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index b83844e20..915169c22 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -125,7 +125,13 @@ def callback(group): extras[2](extras_group) # collect all the remaining arguments into a list - parser.add_argument('args', nargs=argparse.REMAINDER) + parser.add_argument( + "args", + nargs=argparse.REMAINDER, + help=_( + "File to extecute and additional arguments passed on to the executed script." + ), + ) try: options = parser.parse_args(args) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 94676edb8..ae780f7d7 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -150,14 +150,14 @@ def curtsies_arguments(parser): config, options, exec_args = bpargs.parse( args, ( - "curtsies options", - None, + _("curtsies arguments"), + _("Additional arguments specific to the curtsies-based REPL."), curtsies_arguments, ), ) if options.log is None: options.log = 0 - logging_levels = [logging.ERROR, logging.INFO, logging.DEBUG] + logging_levels = (logging.ERROR, logging.INFO, logging.DEBUG) level = logging_levels[min(len(logging_levels) - 1, options.log)] logging.getLogger("curtsies").setLevel(level) logging.getLogger("bpython").setLevel(level) From e50ce9d1b278730b0b03c601a3b69b7f57c4d9c4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Oct 2020 15:15:04 +0100 Subject: [PATCH 1085/1650] Remove unused global repl --- bpython/curtsies.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index ae780f7d7..98c064bd2 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -21,10 +21,6 @@ logger = logging.getLogger(__name__) -repl = None # global for `from bpython.curtsies import repl` -# WARNING Will be a problem if more than one repl is ever instantiated this way - - class FullCurtsiesRepl(BaseRepl): def __init__(self, config, locals_, banner, interp=None): self.input_generator = curtsies.input.Input( @@ -197,7 +193,6 @@ def curtsies_arguments(parser): if banner is not None: print(banner) - global repl repl = FullCurtsiesRepl(config, locals_, welcome_message, interp) try: with repl.input_generator: From 0c6864be1fcdbc17a9790f15e24ed6e36ed0121a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 27 Oct 2020 15:21:13 +0100 Subject: [PATCH 1086/1650] Use super() where possible --- bpython/urwid.py | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index cc7d22f56..d9e0a388c 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1,4 +1,3 @@ -# # The MIT License # # Copyright (c) 2010-2011 Marien Zwart @@ -38,16 +37,12 @@ import signal import urwid -from pygments.token import Token - from . import args as bpargs, repl, translations from .formatter import theme_map from .importcompletion import find_coroutine from .translations import _ from .keys import urwid_key_dispatch as key_dispatch -Parenthesis = Token.Punctuation.Parenthesis - # Urwid colors are: # 'black', 'dark red', 'dark green', 'brown', 'dark blue', # 'dark magenta', 'dark cyan', 'light gray', 'dark gray', @@ -147,7 +142,7 @@ class StatusbarEdit(urwid.Edit): def __init__(self, *args, **kwargs): self.single = False - urwid.Edit.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) def keypress(self, size, key): if self.single: @@ -155,14 +150,13 @@ def keypress(self, size, key): elif key == "enter": urwid.emit_signal(self, "prompt_enter", self, self.get_edit_text()) else: - return urwid.Edit.keypress(self, size, key) + return super().keypress(size, key) urwid.register_signal(StatusbarEdit, "prompt_enter") class Statusbar: - """Statusbar object, ripped off from bpython.cli. This class provides the status bar at the bottom of the screen. @@ -290,7 +284,6 @@ def format_tokens(tokensource): class BPythonEdit(urwid.Edit): - """Customized editor *very* tightly interwoven with URWIDRepl. Changes include: @@ -322,10 +315,10 @@ def __init__(self, config, *args, **kwargs): self._bpy_may_move_cursor = False self.config = config self.tab_length = config.tab_length - urwid.Edit.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) def set_edit_pos(self, pos): - urwid.Edit.set_edit_pos(self, pos) + super().set_edit_pos(pos) self._emit("edit-pos-changed", self.edit_pos) def get_edit_pos(self): @@ -366,7 +359,7 @@ def get_cursor_coords(self, *args, **kwargs): # urwid gets confused if a nonselectable widget has a cursor position. if not self._bpy_selectable: return None - return urwid.Edit.get_cursor_coords(self, *args, **kwargs) + return super().get_cursor_coords(*args, **kwargs) def render(self, size, focus=False): # XXX I do not want to have to do this, but listbox gets confused @@ -374,17 +367,17 @@ def render(self, size, focus=False): # we just became unselectable, then having this render a cursor) if not self._bpy_selectable: focus = False - return urwid.Edit.render(self, size, focus=focus) + return super().render(size, focus=focus) def get_pref_col(self, size): # Need to make this deal with us being nonselectable if not self._bpy_selectable: return "left" - return urwid.Edit.get_pref_col(self, size) + return super().get_pref_col(size) def move_cursor_to_coords(self, *args): if self._bpy_may_move_cursor: - return urwid.Edit.move_cursor_to_coords(self, *args) + return super().move_cursor_to_coords(*args) return False def keypress(self, size, key): @@ -425,10 +418,10 @@ def keypress(self, size, key): if not (cpos or len(line) % self.tab_length or line.strip()): self.set_edit_text(line[: -self.tab_length]) else: - return urwid.Edit.keypress(self, size, key) + return super().keypress(size, key) else: # TODO: Add in specific keypress fetching code here - return urwid.Edit.keypress(self, size, key) + return super().keypress(size, key) return None finally: self._bpy_may_move_cursor = False @@ -436,7 +429,7 @@ def keypress(self, size, key): def mouse_event(self, *args): self._bpy_may_move_cursor = True try: - return urwid.Edit.mouse_event(self, *args) + return super().mouse_event(*args) finally: self._bpy_may_move_cursor = False @@ -453,7 +446,6 @@ def keypress(self, size, key): class Tooltip(urwid.BoxWidget): - """Container inspired by Overlay to position our tooltip. bottom_w should be a BoxWidget. @@ -466,7 +458,7 @@ class Tooltip(urwid.BoxWidget): """ def __init__(self, bottom_w, listbox): - self.__super.__init__() + super().__init__() self.bottom_w = bottom_w self.listbox = listbox @@ -534,7 +526,7 @@ def render(self, size, focus=False): class URWIDInteraction(repl.Interaction): def __init__(self, config, statusbar, frame): - repl.Interaction.__init__(self, config, statusbar) + super().__init__(config, statusbar) self.frame = frame urwid.connect_signal(statusbar, "prompt_result", self._prompt_result) self.callback = None @@ -577,7 +569,7 @@ class URWIDRepl(repl.Repl): _time_between_redraws = 0.05 # seconds def __init__(self, event_loop, palette, interpreter, config): - repl.Repl.__init__(self, interpreter, config) + super().__init__(interpreter, config) self._redraw_handle = None self._redraw_pending = False From 794bfa993102fd810fdd81750b629ce3e69d7f73 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 29 Oct 2020 12:17:59 +0100 Subject: [PATCH 1087/1650] Move babel integration to setup.cfg --- setup.cfg | 19 +++++++++++++++++++ setup.py | 53 +++++------------------------------------------------ 2 files changed, 24 insertions(+), 48 deletions(-) diff --git a/setup.cfg b/setup.cfg index 8183238ab..06135236a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,21 @@ [metadata] license_files = LICENSE + +[init_catalog] +domain = bpython +input_file = bpython/translations/bpython.pot +output_dir = bpython/translations + +[compile_catalog] +domain = bpython +directory = bpython/translations +use_fuzzy = true + +[update_catalog] +domain = bpython +input_file = bpython/translations/bpython.pot +output_dir = bpython/translations + +[extract_messages] +output_file = bpython/translations/bpython.pot +msgid_bugs_address = https://github.com/bpython/bpython/issues diff --git a/setup.py b/setup.py index 33913d2d1..2d50bed2b 100755 --- a/setup.py +++ b/setup.py @@ -10,10 +10,7 @@ from setuptools.command.install import install as _install try: - from babel.messages.frontend import compile_catalog as _compile_catalog - from babel.messages.frontend import extract_messages as _extract_messages - from babel.messages.frontend import update_catalog as _update_catalog - from babel.messages.frontend import init_catalog as _init_catalog + from babel.messages import frontend as babel using_translations = True except ImportError: @@ -136,52 +133,12 @@ def run(self): # localization options if using_translations: - - class compile_catalog(_compile_catalog): - def initialize_options(self): - """Simply set default domain and directory attributes to the - correct path for bpython.""" - _compile_catalog.initialize_options(self) - - self.domain = "bpython" - self.directory = translations_dir - self.use_fuzzy = True - - class update_catalog(_update_catalog): - def initialize_options(self): - """Simply set default domain and directory attributes to the - correct path for bpython.""" - _update_catalog.initialize_options(self) - - self.domain = "bpython" - self.output_dir = translations_dir - self.input_file = os.path.join(translations_dir, "bpython.pot") - - class extract_messages(_extract_messages): - def initialize_options(self): - """Simply set default domain and output file attributes to the - correct values for bpython.""" - _extract_messages.initialize_options(self) - - self.domain = "bpython" - self.output_file = os.path.join(translations_dir, "bpython.pot") - - class init_catalog(_init_catalog): - def initialize_options(self): - """Simply set default domain, input file and output directory - attributes to the correct values for bpython.""" - _init_catalog.initialize_options(self) - - self.domain = "bpython" - self.output_dir = translations_dir - self.input_file = os.path.join(translations_dir, "bpython.pot") - build.sub_commands.insert(0, ("compile_catalog", None)) - cmdclass["compile_catalog"] = compile_catalog - cmdclass["extract_messages"] = extract_messages - cmdclass["update_catalog"] = update_catalog - cmdclass["init_catalog"] = init_catalog + cmdclass["compile_catalog"] = babel.compile_catalog + cmdclass["extract_messages"] = babel.extract_messages + cmdclass["update_catalog"] = babel.update_catalog + cmdclass["init_catalog"] = babel.init_catalog if using_sphinx: From 9d8f414ff7cf566233f88ad5c44b756d21b2cf8b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 29 Oct 2020 12:23:14 +0100 Subject: [PATCH 1088/1650] Update translations --- bpython/translations/bpython.pot | 143 +++++++++-------- .../translations/de/LC_MESSAGES/bpython.po | 147 ++++++++++-------- .../translations/es_ES/LC_MESSAGES/bpython.po | 144 +++++++++-------- .../translations/fr_FR/LC_MESSAGES/bpython.po | 143 +++++++++-------- .../translations/it_IT/LC_MESSAGES/bpython.po | 139 +++++++++-------- .../translations/nl_NL/LC_MESSAGES/bpython.po | 144 +++++++++-------- 6 files changed, 472 insertions(+), 388 deletions(-) diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 443101f32..e8dc384fa 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.21.dev33\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2020-10-13 21:53+0200\n" +"Project-Id-Version: bpython 0.21.dev78\n" +"Report-Msgid-Bugs-To: https://github.com/bpython/bpython/issues\n" +"POT-Creation-Date: 2020-10-29 12:28+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,34 +17,41 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:92 +#, python-format msgid "" -"Usage: %prog [options] [file [args]]\n" +"Usage: %(prog)s [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:78 +#: bpython/args.py:102 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:84 +#: bpython/args.py:108 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:92 +#: bpython/args.py:114 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:98 +#: bpython/args.py:120 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/args.py:131 +msgid "" +"File to extecute and additional arguments passed on to the executed " +"script." +msgstr "" + +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "y" msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "yes" msgstr "" @@ -75,242 +82,250 @@ msgid "" " future version." msgstr "" -#: bpython/curtsies.py:147 +#: bpython/curtsies.py:137 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:153 +#: bpython/curtsies.py:143 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:226 +#: bpython/curtsies.py:149 +msgid "curtsies arguments" +msgstr "" + +#: bpython/curtsies.py:150 +msgid "Additional arguments specific to the curtsies-based REPL." +msgstr "" + +#: bpython/history.py:224 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:86 +#: bpython/paste.py:85 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:88 +#: bpython/paste.py:87 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:94 +#: bpython/paste.py:93 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:99 +#: bpython/paste.py:98 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:106 +#: bpython/paste.py:105 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:823 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:839 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:847 +#: bpython/repl.py:844 msgid "overwrite" msgstr "" -#: bpython/repl.py:849 +#: bpython/repl.py:846 msgid "append" msgstr "" -#: bpython/repl.py:861 bpython/repl.py:1171 +#: bpython/repl.py:858 bpython/repl.py:1168 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:863 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:869 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:876 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:878 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:887 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:889 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:897 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:903 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:907 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:916 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:921 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:960 +#: bpython/repl.py:957 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:965 bpython/repl.py:969 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:975 +#: bpython/repl.py:972 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1148 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1181 +#: bpython/repl.py:1178 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1187 +#: bpython/repl.py:1184 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:618 +#: bpython/urwid.py:605 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1115 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1120 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1149 +#: bpython/urwid.py:1128 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1154 +#: bpython/urwid.py:1133 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1163 +#: bpython/urwid.py:1142 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1350 +#: bpython/urwid.py:1336 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:343 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:342 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:671 +#: bpython/curtsiesfrontend/repl.py:674 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:689 +#: bpython/curtsiesfrontend/repl.py:692 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:998 +#: bpython/curtsiesfrontend/repl.py:1001 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1013 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1026 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1034 +#: bpython/curtsiesfrontend/repl.py:1037 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1040 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1045 +#: bpython/curtsiesfrontend/repl.py:1048 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1051 +#: bpython/curtsiesfrontend/repl.py:1054 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 7023785e6..951c1cc0b 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-10-13 21:53+0200\n" -"PO-Revision-Date: 2020-10-19 20:59+0200\n" +"POT-Creation-Date: 2020-10-29 12:28+0100\n" +"PO-Revision-Date: 2020-10-29 12:26+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: de\n" "Language-Team: de \n" @@ -18,37 +18,44 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:92 +#, python-format msgid "" -"Usage: %prog [options] [file [args]]\n" +"Usage: %(prog)s [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -"Verwendung: %prog [Optionen] [Datei [Argumente]]\n" +"Verwendung: %(prog)s [Optionen] [Datei [Argumente]]\n" "Hinweis: Wenn bpython Argumente übergeben bekommt, die nicht verstanden " "werden, wird der normale Python Interpreter ausgeführt." -#: bpython/args.py:78 +#: bpython/args.py:102 msgid "Use CONFIG instead of default config file." msgstr "Verwende CONFIG antatt der standardmäßigen Konfigurationsdatei." -#: bpython/args.py:84 +#: bpython/args.py:108 msgid "Drop to bpython shell after running file instead of exiting." msgstr "Verbleibe in bpython nach dem Ausführen der Datei." -#: bpython/args.py:92 +#: bpython/args.py:114 msgid "Don't flush the output to stdout." msgstr "Gib Ausgabe beim Beenden nicht ernaut auf stdout aus." -#: bpython/args.py:98 +#: bpython/args.py:120 msgid "Print version and exit." msgstr "Zeige Versionsinformationen an und beende." -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/args.py:131 +msgid "" +"File to extecute and additional arguments passed on to the executed " +"script." +msgstr "" + +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "y" msgstr "j" -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "yes" msgstr "ja" @@ -82,189 +89,197 @@ msgstr "" "`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " "unterstützt und wird in einer zukünftigen Version entfernt werden." -#: bpython/curtsies.py:147 +#: bpython/curtsies.py:137 msgid "log debug messages to bpython.log" msgstr "Zeichne debug Nachrichten in bpython.log auf" -#: bpython/curtsies.py:153 +#: bpython/curtsies.py:143 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:226 +#: bpython/curtsies.py:149 +msgid "curtsies arguments" +msgstr "" + +#: bpython/curtsies.py:150 +msgid "Additional arguments specific to the curtsies-based REPL." +msgstr "Zusätzliche Argumente spezifisch für die curtsies-basierte REPL." + +#: bpython/history.py:224 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" -#: bpython/paste.py:86 +#: bpython/paste.py:85 msgid "Helper program not found." msgstr "Hilfsprogramm konnte nicht gefunden werden." -#: bpython/paste.py:88 +#: bpython/paste.py:87 msgid "Helper program could not be run." msgstr "Hilfsprogramm konnte nicht ausgeführt werden." -#: bpython/paste.py:94 +#: bpython/paste.py:93 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "Hilfsprogramm beendete mit Status %d." -#: bpython/paste.py:99 +#: bpython/paste.py:98 msgid "No output from helper program." msgstr "Keine Ausgabe von Hilfsprogramm vorhanden." -#: bpython/paste.py:106 +#: bpython/paste.py:105 msgid "Failed to recognize the helper program's output as an URL." msgstr "Konnte Ausgabe von Hilfsprogramm nicht verarbeiten." -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "Nichts um Quellcode abzurufen" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" -#: bpython/repl.py:823 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "In Datei speichern (Esc um abzubrechen): " -#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "Speichern abgebrochen." -#: bpython/repl.py:839 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " -msgstr "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen?" +msgstr "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen? " -#: bpython/repl.py:847 +#: bpython/repl.py:844 msgid "overwrite" msgstr "überschreiben" -#: bpython/repl.py:849 +#: bpython/repl.py:846 msgid "append" msgstr "anhängen" -#: bpython/repl.py:861 bpython/repl.py:1171 +#: bpython/repl.py:858 bpython/repl.py:1168 #, python-format msgid "Error writing file '%s': %s" msgstr "Fehler beim Schreiben in Datei '%s': %s" -#: bpython/repl.py:863 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "Nach %s gespeichert." -#: bpython/repl.py:869 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/repl.py:876 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/repl.py:878 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:887 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:889 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:897 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:903 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." -#: bpython/repl.py:907 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:916 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:921 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:960 +#: bpython/repl.py:957 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -"Wie viele Zeilen rückgängig machen? (Rückgängigmachen wird bis zu ~%1.f " +"Wie viele Zeilen rückgängig machen? (Rückgängigmachen wird bis zu ~%.1f " "Sekunden brauchen) [1]" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:965 bpython/repl.py:969 msgid "Undo canceled" msgstr "Rückgängigmachen abgebrochen" -#: bpython/repl.py:975 +#: bpython/repl.py:972 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "Mache %d Zeile rückgängig... (ungefähr %.1f Sekunden)" msgstr[1] "Mache %d Zeilen rückgängig... (ungefähr %.1f Sekunden)" -#: bpython/repl.py:1151 +#: bpython/repl.py:1148 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" "Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " "werden? (j/N)" -#: bpython/repl.py:1181 +#: bpython/repl.py:1178 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" "bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " "Änderungen übernommen werden." -#: bpython/repl.py:1187 +#: bpython/repl.py:1184 #, python-format msgid "Error editing config file: %s" msgstr "Fehler beim Bearbeiten der Konfigurationsdatei: %s" -#: bpython/urwid.py:618 +#: bpython/urwid.py:605 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rückgängigmachen <%s> Speichern <%s> Pastebin <%s> Pager <%s> " "Quellcode anzeigen " -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1115 msgid "Run twisted reactor." msgstr "Führe twisted reactor aus." -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1120 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "Wähle reactor aus (siehe --help-reactors). Impliziert --twisted." -#: bpython/urwid.py:1149 +#: bpython/urwid.py:1128 msgid "List available reactors for -r." msgstr "Liste verfügbare reactors für -r auf." -#: bpython/urwid.py:1154 +#: bpython/urwid.py:1133 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." @@ -272,11 +287,11 @@ msgstr "" "Auszuführendes twistd Plugin (starte twistd für eine Liste). Verwende " "\"--\" um Optionen an das Plugin zu übergeben." -#: bpython/urwid.py:1163 +#: bpython/urwid.py:1142 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1350 +#: bpython/urwid.py:1336 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" @@ -286,51 +301,51 @@ msgstr "" "`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " "unterstützt und wird in einer zukünftigen Version entfernt werden." -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:343 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" -#: bpython/curtsiesfrontend/repl.py:342 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#: bpython/curtsiesfrontend/repl.py:671 +#: bpython/curtsiesfrontend/repl.py:674 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" -#: bpython/curtsiesfrontend/repl.py:689 +#: bpython/curtsiesfrontend/repl.py:692 #, python-format msgid "Reloaded at %s because %s modified." msgstr "Bei %s neugeladen, da %s modifiziert wurde." -#: bpython/curtsiesfrontend/repl.py:998 +#: bpython/curtsiesfrontend/repl.py:1001 msgid "Session not reevaluated because it was not edited" msgstr "Die Sitzung wurde nicht neu ausgeführt, da sie nicht berabeitet wurde" -#: bpython/curtsiesfrontend/repl.py:1013 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session not reevaluated because saved file was blank" msgstr "Die Sitzung wurde nicht neu ausgeführt, da die gespeicherte Datei leer war" -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1026 msgid "Session edited and reevaluated" msgstr "Sitzung bearbeitet und neu ausgeführt" -#: bpython/curtsiesfrontend/repl.py:1034 +#: bpython/curtsiesfrontend/repl.py:1037 #, python-format msgid "Reloaded at %s by user." msgstr "Bei %s vom Benutzer neu geladen." -#: bpython/curtsiesfrontend/repl.py:1040 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading deactivated." msgstr "Automatisches Neuladen deaktiviert." -#: bpython/curtsiesfrontend/repl.py:1045 +#: bpython/curtsiesfrontend/repl.py:1048 msgid "Auto-reloading active, watching for file changes..." msgstr "Automatisches Neuladen ist aktiv; beobachte Dateiänderungen..." -#: bpython/curtsiesfrontend/repl.py:1051 +#: bpython/curtsiesfrontend/repl.py:1054 msgid "Auto-reloading not available because watchdog not installed." msgstr "" "Automatisches Neuladen ist nicht verfügbar da watchdog nicht installiert " diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 57057a420..7bb910396 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-10-13 21:53+0200\n" -"PO-Revision-Date: 2015-02-02 00:34+0100\n" +"POT-Creation-Date: 2020-10-29 12:28+0100\n" +"PO-Revision-Date: 2020-10-29 12:22+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: es_ES\n" "Language-Team: bpython developers\n" @@ -18,34 +18,41 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:92 +#, python-format msgid "" -"Usage: %prog [options] [file [args]]\n" +"Usage: %(prog)s [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:78 +#: bpython/args.py:102 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:84 +#: bpython/args.py:108 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:92 +#: bpython/args.py:114 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:98 +#: bpython/args.py:120 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/args.py:131 +msgid "" +"File to extecute and additional arguments passed on to the executed " +"script." +msgstr "" + +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "y" msgstr "s" -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "yes" msgstr "si" @@ -76,247 +83,252 @@ msgid "" " future version." msgstr "" -#: bpython/curtsies.py:147 +#: bpython/curtsies.py:137 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:153 +#: bpython/curtsies.py:143 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:226 +#: bpython/curtsies.py:149 +msgid "curtsies arguments" +msgstr "" + +#: bpython/curtsies.py:150 +msgid "Additional arguments specific to the curtsies-based REPL." +msgstr "" + +#: bpython/history.py:224 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:86 +#: bpython/paste.py:85 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:88 +#: bpython/paste.py:87 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:94 +#: bpython/paste.py:93 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:99 +#: bpython/paste.py:98 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:106 +#: bpython/paste.py:105 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:823 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:839 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:847 +#: bpython/repl.py:844 msgid "overwrite" msgstr "" -#: bpython/repl.py:849 +#: bpython/repl.py:846 msgid "append" msgstr "" -#: bpython/repl.py:861 bpython/repl.py:1171 +#: bpython/repl.py:858 bpython/repl.py:1168 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:863 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:869 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:876 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:878 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:887 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:889 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:897 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:903 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:907 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:916 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:921 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:960 +#: bpython/repl.py:957 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:965 bpython/repl.py:969 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:975 +#: bpython/repl.py:972 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1148 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1181 +#: bpython/repl.py:1178 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1187 +#: bpython/repl.py:1184 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:618 +#: bpython/urwid.py:605 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra el " "código fuente" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1115 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1120 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1149 +#: bpython/urwid.py:1128 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1154 +#: bpython/urwid.py:1133 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1163 +#: bpython/urwid.py:1142 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1350 +#: bpython/urwid.py:1336 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:343 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:342 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:671 +#: bpython/curtsiesfrontend/repl.py:674 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:689 +#: bpython/curtsiesfrontend/repl.py:692 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:998 +#: bpython/curtsiesfrontend/repl.py:1001 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1013 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1026 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1034 +#: bpython/curtsiesfrontend/repl.py:1037 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1040 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1045 +#: bpython/curtsiesfrontend/repl.py:1048 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1051 +#: bpython/curtsiesfrontend/repl.py:1054 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#~ msgid "Error editing config file." -#~ msgstr "" - diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index 73330837f..d3d00f08a 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-10-13 21:53+0200\n" -"PO-Revision-Date: 2019-09-22 22:58+0200\n" +"POT-Creation-Date: 2020-10-29 12:28+0100\n" +"PO-Revision-Date: 2020-10-29 12:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: fr_FR\n" "Language-Team: bpython developers\n" @@ -17,39 +17,46 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:92 +#, python-format msgid "" -"Usage: %prog [options] [file [args]]\n" +"Usage: %(prog)s [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -"Utilisation: %prog [options] [fichier [arguments]]\n" +"Utilisation: %(prog)s [options] [fichier [arguments]]\n" "NOTE: Si bpython ne reconnaît pas un des arguments fournis, " "l'interpréteur Python classique sera lancé" -#: bpython/args.py:78 +#: bpython/args.py:102 msgid "Use CONFIG instead of default config file." msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." -#: bpython/args.py:84 +#: bpython/args.py:108 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" "Aller dans le shell bpython après l'exécution du fichier au lieu de " "quitter." -#: bpython/args.py:92 +#: bpython/args.py:114 msgid "Don't flush the output to stdout." msgstr "Ne pas purger la sortie vers stdout." -#: bpython/args.py:98 +#: bpython/args.py:120 msgid "Print version and exit." msgstr "Afficher la version et quitter." -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/args.py:131 +msgid "" +"File to extecute and additional arguments passed on to the executed " +"script." +msgstr "" + +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "y" msgstr "o" -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "yes" msgstr "oui" @@ -80,183 +87,191 @@ msgid "" " future version." msgstr "" -#: bpython/curtsies.py:147 +#: bpython/curtsies.py:137 msgid "log debug messages to bpython.log" msgstr "logger les messages de debug dans bpython.log" -#: bpython/curtsies.py:153 +#: bpython/curtsies.py:143 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:226 +#: bpython/curtsies.py:149 +msgid "curtsies arguments" +msgstr "" + +#: bpython/curtsies.py:150 +msgid "Additional arguments specific to the curtsies-based REPL." +msgstr "" + +#: bpython/history.py:224 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" -#: bpython/paste.py:86 +#: bpython/paste.py:85 msgid "Helper program not found." msgstr "programme externe non trouvé." -#: bpython/paste.py:88 +#: bpython/paste.py:87 msgid "Helper program could not be run." msgstr "impossible de lancer le programme externe." -#: bpython/paste.py:94 +#: bpython/paste.py:93 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "le programme externe a renvoyé un statut de sortie différent de zéro %d." -#: bpython/paste.py:99 +#: bpython/paste.py:98 msgid "No output from helper program." msgstr "pas de sortie du programme externe." -#: bpython/paste.py:106 +#: bpython/paste.py:105 msgid "Failed to recognize the helper program's output as an URL." msgstr "la sortie du programme externe ne correspond pas à une URL." -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "Impossible de récupérer le source: %s" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "Impossible d'accéder au source de %r" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "Pas de code source trouvé pour %s" -#: bpython/repl.py:823 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:839 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:847 +#: bpython/repl.py:844 msgid "overwrite" msgstr "" -#: bpython/repl.py:849 +#: bpython/repl.py:846 msgid "append" msgstr "" -#: bpython/repl.py:861 bpython/repl.py:1171 +#: bpython/repl.py:858 bpython/repl.py:1168 #, python-format msgid "Error writing file '%s': %s" msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" -#: bpython/repl.py:863 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:869 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "Pas de presse-papier disponible." -#: bpython/repl.py:876 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "Impossible de copier vers le presse-papier." -#: bpython/repl.py:878 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "Contenu copié vers le presse-papier." -#: bpython/repl.py:887 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "Tampon Pastebin ? (o/N) " -#: bpython/repl.py:889 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "Pastebin abandonné." -#: bpython/repl.py:897 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" -#: bpython/repl.py:903 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "Envoi des donnés à pastebin..." -#: bpython/repl.py:907 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "Echec du téléchargement: %s" -#: bpython/repl.py:916 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "URL Pastebin: %s - URL de suppression: %s" -#: bpython/repl.py:921 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" msgstr "URL Pastebin: %s" -#: bpython/repl.py:960 +#: bpython/repl.py:957 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:965 bpython/repl.py:969 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:975 +#: bpython/repl.py:972 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1148 msgid "Config file does not exist - create new from default? (y/N)" msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" -#: bpython/repl.py:1181 +#: bpython/repl.py:1178 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1187 +#: bpython/repl.py:1184 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:618 +#: bpython/urwid.py:605 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> " "Montrer Source " -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1115 msgid "Run twisted reactor." msgstr "Lancer le reactor twisted." -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1120 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." -#: bpython/urwid.py:1149 +#: bpython/urwid.py:1128 msgid "List available reactors for -r." msgstr "Lister les reactors disponibles pour -r." -#: bpython/urwid.py:1154 +#: bpython/urwid.py:1133 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." @@ -264,62 +279,62 @@ msgstr "" "plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" " "pour donner plus d'options au plugin." -#: bpython/urwid.py:1163 +#: bpython/urwid.py:1142 msgid "Port to run an eval server on (forces Twisted)." msgstr "Port pour lancer un server eval (force Twisted)." -#: bpython/urwid.py:1350 +#: bpython/urwid.py:1336 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:343 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" -#: bpython/curtsiesfrontend/repl.py:342 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." -#: bpython/curtsiesfrontend/repl.py:671 +#: bpython/curtsiesfrontend/repl.py:674 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" -#: bpython/curtsiesfrontend/repl.py:689 +#: bpython/curtsiesfrontend/repl.py:692 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:998 +#: bpython/curtsiesfrontend/repl.py:1001 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1013 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1026 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1034 +#: bpython/curtsiesfrontend/repl.py:1037 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1040 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1045 +#: bpython/curtsiesfrontend/repl.py:1048 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1051 +#: bpython/curtsiesfrontend/repl.py:1054 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index 7c94712da..7d0a39783 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-10-13 21:53+0200\n" +"POT-Creation-Date: 2020-10-29 12:28+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: it_IT\n" @@ -18,34 +18,41 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:92 +#, python-format msgid "" -"Usage: %prog [options] [file [args]]\n" +"Usage: %(prog)s [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:78 +#: bpython/args.py:102 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:84 +#: bpython/args.py:108 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:92 +#: bpython/args.py:114 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:98 +#: bpython/args.py:120 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/args.py:131 +msgid "" +"File to extecute and additional arguments passed on to the executed " +"script." +msgstr "" + +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "y" msgstr "s" -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "yes" msgstr "si" @@ -76,242 +83,250 @@ msgid "" " future version." msgstr "" -#: bpython/curtsies.py:147 +#: bpython/curtsies.py:137 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:153 +#: bpython/curtsies.py:143 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:226 +#: bpython/curtsies.py:149 +msgid "curtsies arguments" +msgstr "" + +#: bpython/curtsies.py:150 +msgid "Additional arguments specific to the curtsies-based REPL." +msgstr "" + +#: bpython/history.py:224 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:86 +#: bpython/paste.py:85 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:88 +#: bpython/paste.py:87 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:94 +#: bpython/paste.py:93 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:99 +#: bpython/paste.py:98 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:106 +#: bpython/paste.py:105 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:823 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:839 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:847 +#: bpython/repl.py:844 msgid "overwrite" msgstr "" -#: bpython/repl.py:849 +#: bpython/repl.py:846 msgid "append" msgstr "" -#: bpython/repl.py:861 bpython/repl.py:1171 +#: bpython/repl.py:858 bpython/repl.py:1168 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:863 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:869 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:876 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:878 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:887 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:889 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:897 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:903 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:907 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:916 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:921 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:960 +#: bpython/repl.py:957 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:965 bpython/repl.py:969 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:975 +#: bpython/repl.py:972 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1148 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1181 +#: bpython/repl.py:1178 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1187 +#: bpython/repl.py:1184 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:618 +#: bpython/urwid.py:605 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1115 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1120 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1149 +#: bpython/urwid.py:1128 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1154 +#: bpython/urwid.py:1133 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1163 +#: bpython/urwid.py:1142 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1350 +#: bpython/urwid.py:1336 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:343 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:342 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:671 +#: bpython/curtsiesfrontend/repl.py:674 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:689 +#: bpython/curtsiesfrontend/repl.py:692 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:998 +#: bpython/curtsiesfrontend/repl.py:1001 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1013 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1026 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1034 +#: bpython/curtsiesfrontend/repl.py:1037 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1040 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1045 +#: bpython/curtsiesfrontend/repl.py:1048 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1051 +#: bpython/curtsiesfrontend/repl.py:1054 msgid "Auto-reloading not available because watchdog not installed." msgstr "" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index eab83d1ae..44f047281 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-10-13 21:53+0200\n" -"PO-Revision-Date: 2015-02-02 00:34+0100\n" +"POT-Creation-Date: 2020-10-29 12:28+0100\n" +"PO-Revision-Date: 2020-10-29 12:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: nl_NL\n" "Language-Team: bpython developers\n" @@ -18,34 +18,41 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:63 +#: bpython/args.py:92 +#, python-format msgid "" -"Usage: %prog [options] [file [args]]\n" +"Usage: %(prog)s [options] [file [args]]\n" "NOTE: If bpython sees an argument it does not know, execution falls back " "to the regular Python interpreter." msgstr "" -#: bpython/args.py:78 +#: bpython/args.py:102 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:84 +#: bpython/args.py:108 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:92 +#: bpython/args.py:114 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:98 +#: bpython/args.py:120 msgid "Print version and exit." msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/args.py:131 +msgid "" +"File to extecute and additional arguments passed on to the executed " +"script." +msgstr "" + +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "y" msgstr "j" -#: bpython/cli.py:315 bpython/urwid.py:551 +#: bpython/cli.py:315 bpython/urwid.py:538 msgid "yes" msgstr "ja" @@ -76,245 +83,250 @@ msgid "" " future version." msgstr "" -#: bpython/curtsies.py:147 +#: bpython/curtsies.py:137 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:153 +#: bpython/curtsies.py:143 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/history.py:226 +#: bpython/curtsies.py:149 +msgid "curtsies arguments" +msgstr "" + +#: bpython/curtsies.py:150 +msgid "Additional arguments specific to the curtsies-based REPL." +msgstr "" + +#: bpython/history.py:224 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" -#: bpython/paste.py:86 +#: bpython/paste.py:85 msgid "Helper program not found." msgstr "" -#: bpython/paste.py:88 +#: bpython/paste.py:87 msgid "Helper program could not be run." msgstr "" -#: bpython/paste.py:94 +#: bpython/paste.py:93 #, python-format msgid "Helper program returned non-zero exit status %d." msgstr "" -#: bpython/paste.py:99 +#: bpython/paste.py:98 msgid "No output from helper program." msgstr "" -#: bpython/paste.py:106 +#: bpython/paste.py:105 msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:823 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:825 bpython/repl.py:828 bpython/repl.py:852 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:839 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:847 +#: bpython/repl.py:844 msgid "overwrite" msgstr "" -#: bpython/repl.py:849 +#: bpython/repl.py:846 msgid "append" msgstr "" -#: bpython/repl.py:861 bpython/repl.py:1171 +#: bpython/repl.py:858 bpython/repl.py:1168 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:863 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:869 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:876 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:878 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:887 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:889 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:897 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:903 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:907 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:916 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:921 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:960 +#: bpython/repl.py:957 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:965 bpython/repl.py:969 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:975 +#: bpython/repl.py:972 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1148 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1181 +#: bpython/repl.py:1178 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1187 +#: bpython/repl.py:1184 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:618 +#: bpython/urwid.py:605 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" -#: bpython/urwid.py:1136 +#: bpython/urwid.py:1115 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1120 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1149 +#: bpython/urwid.py:1128 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1154 +#: bpython/urwid.py:1133 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1163 +#: bpython/urwid.py:1142 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1350 +#: bpython/urwid.py:1336 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:340 +#: bpython/curtsiesfrontend/repl.py:343 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:342 +#: bpython/curtsiesfrontend/repl.py:345 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:671 +#: bpython/curtsiesfrontend/repl.py:674 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:689 +#: bpython/curtsiesfrontend/repl.py:692 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:998 +#: bpython/curtsiesfrontend/repl.py:1001 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1013 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1026 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1034 +#: bpython/curtsiesfrontend/repl.py:1037 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1040 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1045 +#: bpython/curtsiesfrontend/repl.py:1048 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1051 +#: bpython/curtsiesfrontend/repl.py:1054 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#~ msgid "Error editing config file." -#~ msgstr "" - From 9c143fa43bfd556e94d3f3775f5de5e1af4525e2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 29 Oct 2020 12:18:46 +0100 Subject: [PATCH 1089/1650] Re-use authors from bpython --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2d50bed2b..78b6b1a21 100755 --- a/setup.py +++ b/setup.py @@ -127,7 +127,7 @@ def run(self): cmdclass = {"build": build, "install": install} -from bpython import package_dir +from bpython import package_dir, __author__ translations_dir = os.path.join(package_dir, "translations") @@ -227,7 +227,7 @@ def initialize_options(self): setup( name="bpython", version=version, - author="Bob Farrell, Andreas Stuehrk et al.", + author=__author__, author_email="robertanthonyfarrell@gmail.com", description="Fancy Interface to the Python Interpreter", license="MIT/X", From 29b1baeb4fd68a724627bfcf6b6db4e8b892c472 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 29 Oct 2020 17:33:12 +0100 Subject: [PATCH 1090/1650] Use super --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 78b6b1a21..4f47d5779 100755 --- a/setup.py +++ b/setup.py @@ -122,7 +122,7 @@ class install(_install): def run(self): self.run_command("build") - _install.run(self) + super().run() cmdclass = {"build": build, "install": install} @@ -144,7 +144,7 @@ def run(self): class BuildDocMan(BuildDoc): def initialize_options(self): - BuildDoc.initialize_options(self) + super().initialize_options() self.builder = "man" self.source_dir = "doc/sphinx/source" self.build_dir = "build" From 2ead56df21c0c8b73815c94313ec66285bf4f2e4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 29 Oct 2020 17:35:55 +0100 Subject: [PATCH 1091/1650] Remove obsolete install target fix up --- setup.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 4f47d5779..a2328643c 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,6 @@ from distutils.command.build import build from setuptools import setup -from setuptools.command.install import install as _install try: from babel.messages import frontend as babel @@ -117,15 +116,7 @@ def git_describe_to_python_version(version): vf.write(f"__version__ = \"{version}\"\n") -class install(_install): - """Force install to run build target.""" - - def run(self): - self.run_command("build") - super().run() - - -cmdclass = {"build": build, "install": install} +cmdclass = {"build": build} from bpython import package_dir, __author__ From 627ebc9f6a02d8ca04b7b4241483e98e8e42e408 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 29 Oct 2020 17:38:17 +0100 Subject: [PATCH 1092/1650] Move build_sphinx_man configuration to setup.cfg --- setup.cfg | 5 +++++ setup.py | 12 ++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/setup.cfg b/setup.cfg index 06135236a..bfed3932c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,3 +19,8 @@ output_dir = bpython/translations [extract_messages] output_file = bpython/translations/bpython.pot msgid_bugs_address = https://github.com/bpython/bpython/issues + +[build_sphinx_man] +builder = man +source_dir = doc/sphinx/source +build_dir = build diff --git a/setup.py b/setup.py index a2328643c..4650fd663 100755 --- a/setup.py +++ b/setup.py @@ -132,18 +132,10 @@ def git_describe_to_python_version(version): cmdclass["init_catalog"] = babel.init_catalog if using_sphinx: - - class BuildDocMan(BuildDoc): - def initialize_options(self): - super().initialize_options() - self.builder = "man" - self.source_dir = "doc/sphinx/source" - self.build_dir = "build" - build.sub_commands.insert(0, ("build_sphinx_man", None)) - cmdclass["build_sphinx_man"] = BuildDocMan + cmdclass["build_sphinx_man"] = BuildDoc - if platform.system() in ["FreeBSD", "OpenBSD"]: + if platform.system() in ("FreeBSD", "OpenBSD"): man_dir = "man" else: man_dir = "share/man" From 081e3f2d8dbdf08e3ea53bdfea115914c48cc002 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 1 Nov 2020 10:26:47 +0100 Subject: [PATCH 1093/1650] Replace type("") with str --- bpython/test/test_autocomplete.py | 2 +- bpython/test/test_curtsies_coderunner.py | 2 +- bpython/test/test_curtsies_repl.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 5b7ccd6ca..852a6ef7b 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -409,7 +409,7 @@ def function(): def test_completions_are_unicode(self): for m in self.com.matches(1, "a", locals_={"abc": 10}): - self.assertIsInstance(m, type("")) + self.assertIsInstance(m, str) def test_mock_kwlist(self): with mock.patch.object(keyword, "kwlist", new=["abcd"]): diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index ec0dbfe0b..3a677b561 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -46,7 +46,7 @@ def ctrlc(): class TestFakeOutput(unittest.TestCase): def assert_unicode(self, s): - self.assertIsInstance(s, type("")) + self.assertIsInstance(s, str) def test_bytes(self): out = FakeOutput(mock.Mock(), self.assert_unicode, None) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index de5fdb96c..824c166e0 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -405,7 +405,7 @@ def setUp(self): self.repl.pager = self.assert_pager_gets_unicode def assert_pager_gets_unicode(self, text): - self.assertIsInstance(text, type("")) + self.assertIsInstance(text, str) def test_help(self): self.repl.pager(self.repl.help_text()) From e66dc0ac766ad38bba41dea9a1612dcda2b2fb5e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Nov 2020 22:04:17 +0100 Subject: [PATCH 1094/1650] Fix check of key code (fixes #859) --- bpython/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 4cedab8d8..218949aab 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -916,11 +916,11 @@ def p_key(self, key): # Redraw (as there might have been highlighted parens) self.print_line(self.s) - elif key in ("KEY_NPAGE"): # page_down + elif key in ("KEY_NPAGE",): # page_down self.hend() self.print_line(self.s) - elif key in ("KEY_PPAGE"): # page_up + elif key in ("KEY_PPAGE",): # page_up self.hbegin() self.print_line(self.s) From 23f681ed65e54bfe9aa494f4be9638081c696834 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 8 Nov 2020 22:04:17 +0100 Subject: [PATCH 1095/1650] Fix check of key code (fixes #859) --- bpython/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 68d7f8566..33563bf6b 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -939,11 +939,11 @@ def p_key(self, key): # Redraw (as there might have been highlighted parens) self.print_line(self.s) - elif key in ("KEY_NPAGE"): # page_down + elif key in ("KEY_NPAGE",): # page_down self.hend() self.print_line(self.s) - elif key in ("KEY_PPAGE"): # page_up + elif key in ("KEY_PPAGE",): # page_up self.hbegin() self.print_line(self.s) From 15580483e5fa587d31396bb516767d8c359bb025 Mon Sep 17 00:00:00 2001 From: supakeen Date: Tue, 10 Nov 2020 20:38:33 +0100 Subject: [PATCH 1096/1650] Prepare 0.20.1 release. --- CHANGELOG | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 3606bbadb..28805a1a6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,12 @@ Changelog ========= +0.20.1 +------ + +Fixes: +* Fix check of key code (fixes #859) + 0.20 ---- From fc9e6fc6ace58dc2b9aa7ad9e3d68a3038974330 Mon Sep 17 00:00:00 2001 From: niloct Date: Wed, 11 Nov 2020 12:06:49 -0300 Subject: [PATCH 1097/1650] Docs state wrong default config file directory Fixing correct default directory for bpython config file (especially useful for color customization). --- doc/sphinx/source/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/configuration.rst b/doc/sphinx/source/configuration.rst index 6b07f8a6e..1f559e15f 100644 --- a/doc/sphinx/source/configuration.rst +++ b/doc/sphinx/source/configuration.rst @@ -4,7 +4,7 @@ Configuration ============= You can edit the config file by pressing F3 (default). If a config file does not exist you will asked if you would like to create a file. By default it will be -saved to ``$XDG_CONFIG_HOME/.config/bpython/config`` [#f1]_. +saved to ``$XDG_CONFIG_HOME/bpython/config`` [#f1]_. .. include:: configuration-options.rst From 4880684e34307d260760caccb521a46fe94f46e1 Mon Sep 17 00:00:00 2001 From: Kelsey Blair Date: Tue, 10 Nov 2020 15:39:09 -0800 Subject: [PATCH 1098/1650] upgrading curtsies version to include changes from PR 111 to fix typing bug --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 397da7e93..16b3574fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Pygments -curtsies >=0.3.0 +curtsies >=0.3.2 greenlet requests setuptools From ff2acf1c02bd392cd292d22ae930e5f5f6348d3b Mon Sep 17 00:00:00 2001 From: Kelsey Blair Date: Tue, 10 Nov 2020 15:43:38 -0800 Subject: [PATCH 1099/1650] upgrade to 0.3.3 (latest) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 16b3574fd..5cb3eba12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Pygments -curtsies >=0.3.2 +curtsies >=0.3.3 greenlet requests setuptools From 16d84e4cadc67f7f3d81dcdb75dc2fde1905e37a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 13 Nov 2020 19:16:01 +0100 Subject: [PATCH 1100/1650] Also bump curtsies in install_requires and README --- README.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index ad9000781..58e4d77b9 100644 --- a/README.rst +++ b/README.rst @@ -97,7 +97,7 @@ Dependencies ============ * Pygments * requests -* curtsies >= 0.3.0 +* curtsies >= 0.3.3 * greenlet * Sphinx >= 1.5 (optional, for the documentation) * babel (optional, for internationalization) diff --git a/setup.py b/setup.py index 4650fd663..c49251f9b 100755 --- a/setup.py +++ b/setup.py @@ -171,7 +171,7 @@ def git_describe_to_python_version(version): install_requires = [ "pygments", "requests", - "curtsies >=0.3.0", + "curtsies >=0.3.3", "greenlet", "wcwidth", ] From b6b66fe648384940afc5d8bcf37c2d9595aa5b34 Mon Sep 17 00:00:00 2001 From: supakeen Date: Sat, 14 Nov 2020 13:59:32 +0100 Subject: [PATCH 1101/1650] Add extension to CHANGELOG and AUTHORS. This makes it easier to find when there's a reference to a file. I've also updated the docs to not be a symlink to changelog but instead an explicit include. --- AUTHORS => AUTHORS.rst | 0 CHANGELOG => CHANGELOG.rst | 0 MANIFEST.in | 4 ++-- bpython/args.py | 2 +- doc/sphinx/source/authors.rst | 2 +- doc/sphinx/source/changelog.rst | 4 +++- 6 files changed, 7 insertions(+), 5 deletions(-) rename AUTHORS => AUTHORS.rst (100%) rename CHANGELOG => CHANGELOG.rst (100%) mode change 120000 => 100644 doc/sphinx/source/changelog.rst diff --git a/AUTHORS b/AUTHORS.rst similarity index 100% rename from AUTHORS rename to AUTHORS.rst diff --git a/CHANGELOG b/CHANGELOG.rst similarity index 100% rename from CHANGELOG rename to CHANGELOG.rst diff --git a/MANIFEST.in b/MANIFEST.in index badb93f1e..8694b9e27 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ include .pycheckrc -include AUTHORS -include CHANGELOG +include AUTHORS.rst +include CHANGELOG.rst include LICENSE include data/bpython.png include data/org.bpython-interpreter.bpython.desktop diff --git a/bpython/args.py b/bpython/args.py index 915169c22..0636dad9f 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -55,7 +55,7 @@ def version_banner(base="bpython"): def copyright_banner(): - return "{} See AUTHORS for details.".format(__copyright__) + return "{} See AUTHORS.rst for details.".format(__copyright__) def parse(args, extras=None, ignore_stdin=False): diff --git a/doc/sphinx/source/authors.rst b/doc/sphinx/source/authors.rst index c83e6aefb..d475229e0 100644 --- a/doc/sphinx/source/authors.rst +++ b/doc/sphinx/source/authors.rst @@ -5,4 +5,4 @@ Authors If you contributed to bpython and want to be on this list please find us (:ref:`community`) and let us know! -.. include:: ../../../AUTHORS +.. include:: ../../../AUTHORS.rst diff --git a/doc/sphinx/source/changelog.rst b/doc/sphinx/source/changelog.rst deleted file mode 120000 index b6b15a7d0..000000000 --- a/doc/sphinx/source/changelog.rst +++ /dev/null @@ -1 +0,0 @@ -../../../CHANGELOG \ No newline at end of file diff --git a/doc/sphinx/source/changelog.rst b/doc/sphinx/source/changelog.rst new file mode 100644 index 000000000..29e651cab --- /dev/null +++ b/doc/sphinx/source/changelog.rst @@ -0,0 +1,3 @@ +.. _changelog: + +.. include:: ../../../CHANGELOG.rst From 165f3cc4d8413e396a11d33df226cd63e12a3746 Mon Sep 17 00:00:00 2001 From: supakeen Date: Sat, 14 Nov 2020 14:01:16 +0100 Subject: [PATCH 1102/1650] Move themes to sub directory. --- light.theme => theme/light.theme | 0 sample.theme => theme/sample.theme | 0 windows.theme => theme/windows.theme | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename light.theme => theme/light.theme (100%) rename sample.theme => theme/sample.theme (100%) rename windows.theme => theme/windows.theme (100%) diff --git a/light.theme b/theme/light.theme similarity index 100% rename from light.theme rename to theme/light.theme diff --git a/sample.theme b/theme/sample.theme similarity index 100% rename from sample.theme rename to theme/sample.theme diff --git a/windows.theme b/theme/windows.theme similarity index 100% rename from windows.theme rename to theme/windows.theme From d7c59621c27ab43f6b399e510cd7b79b13294326 Mon Sep 17 00:00:00 2001 From: supakeen Date: Sat, 14 Nov 2020 14:08:06 +0100 Subject: [PATCH 1103/1650] Refer to `bpython` not `pinnwand`. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 58e4d77b9..13f2a75cb 100644 --- a/README.rst +++ b/README.rst @@ -4,8 +4,8 @@ .. image:: https://travis-ci.org/bpython/bpython.svg?branch=master :target: https://travis-ci.org/bpython/bpython -.. image:: https://readthedocs.org/projects/pinnwand/badge/?version=latest - :target: https://pinnwand.readthedocs.io/en/latest/ +.. image:: https://readthedocs.org/projects/bpython/badge/?version=latest + :target: https://docs.bpython-interpreter.org/en/latest/ .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/ambv/black From 8d91453918236c57c5fc7dbeacff9b81295f6df9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 10 Dec 2020 12:06:24 +0100 Subject: [PATCH 1104/1650] Blacklist more folders for import completion (fixes #866) --- bpython/importcompletion.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 91972a6b5..a753932fb 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -42,7 +42,18 @@ paths = set() # Patterns to skip # TODO: This skiplist should be configurable. -skiplist = (".git", ".config", ".local", ".share", "node_modules") +skiplist = ( + # version tracking + ".git", ".svn", ".hg" + # XDG + ".config", ".local", ".share", + # nodejs + "node_modules", + # PlayOnLinux + "PlayOnLinux's virtual drives", + # wine + "dosdevices", +) fully_loaded = False From 0f313e4104b14b293159d6832055555551fdec98 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 11 Dec 2020 13:15:45 +0100 Subject: [PATCH 1105/1650] Drop slow attribute for tests --- bpython/test/test_args.py | 11 ----------- bpython/test/test_crashers.py | 12 ------------ 2 files changed, 23 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 3e8c5e9db..78242a32e 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -7,18 +7,7 @@ from bpython import args from bpython.test import FixLanguageTestCase as TestCase, unittest -try: - from nose.plugins.attrib import attr -except ImportError: - def attr(*args, **kwargs): - def identity(func): - return func - - return identity - - -@attr(speed="slow") class TestExecArgs(unittest.TestCase): def test_exec_dunder_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 39fce35cb..7a92f4f87 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -28,16 +28,6 @@ class TrialTestCase: except ImportError: have_urwid = False -try: - from nose.plugins.attrib import attr -except ImportError: - - def attr(*args, **kwargs): - def identity(func): - return func - - return identity - def set_win_size(fd, rows, columns): s = struct.pack("HHHH", rows, columns, 0, 0) @@ -109,7 +99,6 @@ def processExited(self, reason): ) return result - @attr(speed="slow") def test_issue108(self): input = textwrap.dedent( """\ @@ -121,7 +110,6 @@ def spam(): deferred = self.run_bpython(input) return deferred.addCallback(self.check_no_traceback) - @attr(speed="slow") def test_issue133(self): input = textwrap.dedent( """\ From 68bba09672a48d64eeec318f8d084112d10d1b12 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 14 Dec 2020 16:56:12 +0100 Subject: [PATCH 1106/1650] Replace travis with Github Actions --- .github/workflows/build.yaml | 44 ++++++++++++++++++++++++++++++++++++ .travis.install.sh | 23 ------------------- .travis.script.sh | 11 --------- .travis.yml | 26 --------------------- 4 files changed, 44 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/build.yaml delete mode 100755 .travis.install.sh delete mode 100755 .travis.script.sh delete mode 100644 .travis.yml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..ee822e154 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,44 @@ +name: CI + +on: + push: + pull_request: + schedule: + # run at 7:00 on the first of every month + - cron: '0 7 1 * *' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-cov urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" + - name: Build with Python ${{ matrix.python-version }} + run: | + python setup.py build + - name: Build documentation + run: | + python setup.py build_sphinx + python setup.py build_sphinx_man + - name: Test with pytest + run: | + pytest --cov=bpython --cov-report=xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + env: + PYTHON_VERSION: ${{ matrix.python-version }} + with: + file: ./coverage.xml + env_vars: PYTHON_VERSION + if: ${{ always() }} diff --git a/.travis.install.sh b/.travis.install.sh deleted file mode 100755 index 40c0a67bb..000000000 --- a/.travis.install.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -set -e -set -x - -pip install setuptools - -if [[ $RUN == nosetests ]]; then - # core dependencies - pip install -r requirements.txt - # urwid specific dependencies - pip install urwid twisted - # filewatch specific dependencies - pip install watchdog - # jedi specific dependencies - pip install 'jedi >= 0.16' - # translation specific dependencies - pip install babel - # build and install - python setup.py install -elif [[ $RUN == build_sphinx ]]; then - # documentation specific dependencies - pip install 'sphinx >=1.5' -fi diff --git a/.travis.script.sh b/.travis.script.sh deleted file mode 100755 index f8619bb48..000000000 --- a/.travis.script.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -set -e -set -x - -if [[ $RUN == build_sphinx ]]; then - python setup.py build_sphinx - python setup.py build_sphinx_man -elif [[ $RUN == nosetests ]]; then - cd build/lib/ - nosetests -v bpython/test -fi diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 04b7750f3..000000000 --- a/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -language: python -sudo: false -dist: bionic - -python: - - "3.9-dev" - - "3.8" - - "3.7" - - "3.6" - - "nightly" - - "pypy3" - -env: - - RUN=nosetests - - RUN=build_sphinx - -matrix: - allow_failures: - - python: "nightly" - - python: "pypy3" - -install: - - ./.travis.install.sh - -script: - - ./.travis.script.sh From 1d930fd280b5ff4586edcae2787ef9ee691fa374 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 14 Dec 2020 17:10:49 +0100 Subject: [PATCH 1107/1650] Skip tests known to fail with pytest --- bpython/test/test_args.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 78242a32e..e71a9ddfd 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -9,6 +9,7 @@ class TestExecArgs(unittest.TestCase): + @unittest.skip("test broken under pytest") def test_exec_dunder_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write( @@ -48,6 +49,7 @@ def test_exec_nonascii_file(self): except subprocess.CalledProcessError: self.fail("Error running module with nonascii characters") + @unittest.skip("test broken under pytest") def test_exec_nonascii_file_linenums(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write( From 4d0190b5233209706f6982790c762ae6e57ea7cb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 14 Dec 2020 17:12:17 +0100 Subject: [PATCH 1108/1650] Document testing with pytest --- doc/sphinx/source/contributing.rst | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 06e656d49..ade107476 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -47,7 +47,7 @@ Next install your development copy of bpython and its dependencies: # install optional dependencies $ pip install watchdog urwid # development dependencies - $ pip install sphinx nose + $ pip install sphinx pytest # this runs your modified copy of bpython! $ bpython @@ -62,7 +62,7 @@ Next install your development copy of bpython and its dependencies: $ sudp apt install python3-greenlet python3-pygments python3-requests $ sudo apt install python3-watchdog python3-urwid - $ sudo apt install python3-sphinx python3-nose + $ sudo apt install python3-sphinx python3-pytest You also need to run `virtualenv` with `--system-site-packages` packages, if you want to use the packages provided by your distribution. @@ -83,14 +83,8 @@ To run tests from the bpython directory: .. code-block:: bash - $ nosetests + $ pytest -If you want to skip test cases that are known to be slow, run `nosetests` in -the following way: - -.. code-block:: bash - - $ nosetests -A "speed != 'slow'" Building the documentation -------------------------- From 1846449e22e69948a3abe2ca6f5afa69c5acc375 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 14 Dec 2020 18:01:47 +0100 Subject: [PATCH 1109/1650] Remove travis badge --- README.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.rst b/README.rst index 13f2a75cb..da947d55a 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,6 @@ .. image:: https://img.shields.io/pypi/v/bpython :target: https://pypi.org/project/bpython -.. image:: https://travis-ci.org/bpython/bpython.svg?branch=master - :target: https://travis-ci.org/bpython/bpython - .. image:: https://readthedocs.org/projects/bpython/badge/?version=latest :target: https://docs.bpython-interpreter.org/en/latest/ From 3e04ddaedb22ded1070ae716a1a414564b93f341 Mon Sep 17 00:00:00 2001 From: Yu Fan Date: Thu, 17 Dec 2020 19:28:44 -0800 Subject: [PATCH 1110/1650] Add installation method using system package managers --- README.rst | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/README.rst b/README.rst index da947d55a..d36342729 100644 --- a/README.rst +++ b/README.rst @@ -42,6 +42,10 @@ You can find more about bpython - including `full documentation`_ - at our ========================== Installation & Basic Usage ========================== + +Installation using Pip +---------------------- + If you have `pip`_ installed, you can simply run: .. code-block:: bash @@ -52,6 +56,64 @@ Start bpython by typing ``bpython`` in your terminal. You can exit bpython by using the ``exit()`` command or by pressing control-D like regular interactive Python. +Installation via OS Package Manager +----------------------------------- + +The majority of operating system of desktop computers comes with a package +manager system, if you are any user of them, you can install ``bpython`` +using the package manager. + +Ubuntu/Debian +~~~~~~~~~~~~~ +Ubuntu/Debian family Linux users and install bpython using the apt package manager, using the +command with sudo priviledge: + +.. code-block:: bash + + $ apt install bpython + +In case you are using an older version, run + +.. code-block:: bash + + $ apt-get install bpython + +Arch Linux +~~~~~~~~~ +Arch linux uses pacman as the default package manager, and you can use it to install bpython: + +.. code-block:: bash + + $ pacman -S bpython + + +Windows +~~~~~~~ +**Caveats:** As ``bpython`` makes use of the ncurses library of \*nix-family operating systems, +bpython on Windows is not officially supported and tested. + +However, you can still use bpython on Windows using a somewhat work around. Briefly, you should install +these two packages using pip: + +.. code-block:: bash + + $ pip install bpython windows-curses + +Then you should invoke a program called ``bpython-curses.exe`` instead of ``bpython.exe`` to use bpython: + +.. code-block:: bash + + $ bpython-curses + +Mac OS +~~~~~~ +Like Windows, Mac OS does not include a package manager by default. If you have installed any +third-party pacakge manager like MacPorts, you can install it via + +.. code-block:: bash + + $ sudo port install py-bpython + =================== Features & Examples =================== From 9d05fc52f423320daad204e0ccf9229fc05e671c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 18 Dec 2020 12:51:02 +0100 Subject: [PATCH 1111/1650] Add missing tilde --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d36342729..2bbb777e7 100644 --- a/README.rst +++ b/README.rst @@ -79,7 +79,7 @@ In case you are using an older version, run $ apt-get install bpython Arch Linux -~~~~~~~~~ +~~~~~~~~~~ Arch linux uses pacman as the default package manager, and you can use it to install bpython: .. code-block:: bash From eef933f1a2422f507eaa675027a0a6283f8fcfe4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 30 Dec 2020 18:22:41 +0100 Subject: [PATCH 1112/1650] Update list of magic methods --- bpython/autocomplete.py | 65 +++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 9b4f72dd2..a93d123ba 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -48,35 +48,51 @@ ALL_MODES = (SIMPLE, SUBSTRING, FUZZY) MAGIC_METHODS = tuple( - "__%s__" % s + f"__{s}__" for s in ( + "new", "init", + "del", "repr", "str", + "bytes", + "format", "lt", "le", "eq", "ne", "gt", "ge", - "cmp", "hash", - "nonzero", - "unicode", + "bool", "getattr", + "getattribute", "setattr", + "delattr", + "dir", "get", "set", + "delete", + "set_name", + "init_subclass", + "instancecheck", + "subclasscheck", + "class_getitem", "call", "len", + "length_hint", "getitem", "setitem", + "delitem", + "missing", "iter", "reversed", "contains", "add", "sub", "mul", + "matmul", + "truediv", "floordiv", "mod", "divmod", @@ -86,8 +102,33 @@ "and", "xor", "or", - "div", - "truediv", + "radd", + "rsub", + "rmul", + "rmatmul", + "rtruediv", + "rfloordiv", + "rmod", + "rdivmod", + "rpow", + "rlshift", + "rrshift", + "rand", + "rxor", + "ror", + "iadd", + "isub", + "imul", + "imatmul", + "itruediv", + "ifloordiv", + "imod", + "ipow", + "ilshift", + "irshift", + "iand", + "ixor", + "ixor", "neg", "pos", "abs", @@ -95,12 +136,18 @@ "complex", "int", "float", - "oct", - "hex", "index", - "coerce", + "round", + "trunc", + "floor", + "ceil", "enter", "exit", + "await", + "aiter", + "anext", + "aenter", + "aexit", ) ) From fab1d4efd6d1449adee55f060fda7379915d5319 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 30 Dec 2020 18:33:24 +0100 Subject: [PATCH 1113/1650] Turn auto completion modes into an Enum --- bpython/autocomplete.py | 29 ++++++++++------- bpython/config.py | 10 ++++-- bpython/curtsiesfrontend/repl.py | 2 +- bpython/test/test_repl.py | 56 ++++++++++++++++++++++++-------- 4 files changed, 68 insertions(+), 29 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a93d123ba..5a273424c 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -32,6 +32,7 @@ import rlcompleter import builtins +from enum import Enum from . import inspection from . import importcompletion from . import line as lineparts @@ -41,11 +42,17 @@ # Autocomplete modes -SIMPLE = "simple" -SUBSTRING = "substring" -FUZZY = "fuzzy" +class AutocompleteModes(Enum): + SIMPLE = "simple" + SUBSTRING = "substring" + FUZZY = "fuzzy" + + @classmethod + def from_string(cls, value): + if value in cls.__members__: + return cls.__members__[value] + return None -ALL_MODES = (SIMPLE, SUBSTRING, FUZZY) MAGIC_METHODS = tuple( f"__{s}__" @@ -189,16 +196,16 @@ def method_match_fuzzy(word, size, text): MODES_MAP = { - SIMPLE: method_match_simple, - SUBSTRING: method_match_substring, - FUZZY: method_match_fuzzy, + AutocompleteModes.SIMPLE: method_match_simple, + AutocompleteModes.SUBSTRING: method_match_substring, + AutocompleteModes.FUZZY: method_match_fuzzy, } class BaseCompletionType: """Describes different completion types""" - def __init__(self, shown_before_tab=True, mode=SIMPLE): + def __init__(self, shown_before_tab=True, mode=AutocompleteModes.SIMPLE): self._shown_before_tab = shown_before_tab self.method_match = MODES_MAP[mode] @@ -248,7 +255,7 @@ def shown_before_tab(self): class CumulativeCompleter(BaseCompletionType): """Returns combined matches from several completers""" - def __init__(self, completers, mode=SIMPLE): + def __init__(self, completers, mode=AutocompleteModes.SIMPLE): if not completers: raise ValueError( "CumulativeCompleter requires at least one completer" @@ -289,7 +296,7 @@ def format(self, word): class FilenameCompletion(BaseCompletionType): - def __init__(self, mode=SIMPLE): + def __init__(self, mode=AutocompleteModes.SIMPLE): super().__init__(False, mode) def safe_glob(self, pathname): @@ -649,7 +656,7 @@ def get_completer(completers, cursor_offset, line, **kwargs): return [], None -def get_default_completer(mode=SIMPLE): +def get_default_completer(mode=AutocompleteModes.SIMPLE): return ( DictKeyCompletion(mode=mode), ImportCompletion(mode=mode), diff --git a/bpython/config.py b/bpython/config.py index 643aca76a..59b12c44e 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -4,7 +4,9 @@ from itertools import chain from configparser import ConfigParser -from .autocomplete import SIMPLE as default_completion, ALL_MODES +from .autocomplete import AutocompleteModes + +default_completion = AutocompleteModes.SIMPLE class Struct: @@ -234,7 +236,9 @@ def get_key_no_doublebind(command): struct.complete_magic_methods = config.getboolean( "general", "complete_magic_methods" ) - struct.autocomplete_mode = config.get("general", "autocomplete_mode") + struct.autocomplete_mode = AutocompleteModes.from_string( + config.get("general", "autocomplete_mode") + ) struct.save_append_py = config.getboolean("general", "save_append_py") struct.curtsies_list_above = config.getboolean("curtsies", "list_above") @@ -282,7 +286,7 @@ def get_key_no_doublebind(command): struct.hist_file = os.path.expanduser(struct.hist_file) # verify completion mode - if struct.autocomplete_mode not in ALL_MODES: + if struct.autocomplete_mode is None: struct.autocomplete_mode = default_completion # set box drawing characters diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 32ddd8362..9af2c4bbc 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -347,7 +347,7 @@ def __init__( else: banner = None # only one implemented currently - config.autocomplete_mode = autocomplete.SIMPLE + config.autocomplete_mode = autocomplete.AutocompleteModes.SIMPLE if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1: config.cli_suggestion_width = 1 diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 868489061..7182416e1 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -364,7 +364,9 @@ def test_push(self): # COMPLETE TESTS # 1. Global tests def test_simple_global_complete(self): - self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE}) + self.repl = FakeRepl( + {"autocomplete_mode": autocomplete.AutocompleteModes.SIMPLE} + ) self.set_input_line("d") self.assertTrue(self.repl.complete()) @@ -375,7 +377,9 @@ def test_simple_global_complete(self): ) def test_substring_global_complete(self): - self.repl = FakeRepl({"autocomplete_mode": autocomplete.SUBSTRING}) + self.repl = FakeRepl( + {"autocomplete_mode": autocomplete.AutocompleteModes.SUBSTRING} + ) self.set_input_line("time") self.assertTrue(self.repl.complete()) @@ -385,7 +389,9 @@ def test_substring_global_complete(self): ) def test_fuzzy_global_complete(self): - self.repl = FakeRepl({"autocomplete_mode": autocomplete.FUZZY}) + self.repl = FakeRepl( + {"autocomplete_mode": autocomplete.AutocompleteModes.FUZZY} + ) self.set_input_line("doc") self.assertTrue(self.repl.complete()) @@ -397,7 +403,9 @@ def test_fuzzy_global_complete(self): # 2. Attribute tests def test_simple_attribute_complete(self): - self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE}) + self.repl = FakeRepl( + {"autocomplete_mode": autocomplete.AutocompleteModes.SIMPLE} + ) self.set_input_line("Foo.b") code = "class Foo():\n\tdef bar(self):\n\t\tpass\n" @@ -409,7 +417,9 @@ def test_simple_attribute_complete(self): self.assertEqual(self.repl.matches_iter.matches, ["Foo.bar"]) def test_substring_attribute_complete(self): - self.repl = FakeRepl({"autocomplete_mode": autocomplete.SUBSTRING}) + self.repl = FakeRepl( + {"autocomplete_mode": autocomplete.AutocompleteModes.SUBSTRING} + ) self.set_input_line("Foo.az") code = "class Foo():\n\tdef baz(self):\n\t\tpass\n" @@ -421,7 +431,9 @@ def test_substring_attribute_complete(self): self.assertEqual(self.repl.matches_iter.matches, ["Foo.baz"]) def test_fuzzy_attribute_complete(self): - self.repl = FakeRepl({"autocomplete_mode": autocomplete.FUZZY}) + self.repl = FakeRepl( + {"autocomplete_mode": autocomplete.AutocompleteModes.FUZZY} + ) self.set_input_line("Foo.br") code = "class Foo():\n\tdef bar(self):\n\t\tpass\n" @@ -434,7 +446,9 @@ def test_fuzzy_attribute_complete(self): # 3. Edge cases def test_updating_namespace_complete(self): - self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE}) + self.repl = FakeRepl( + {"autocomplete_mode": autocomplete.AutocompleteModes.SIMPLE} + ) self.set_input_line("foo") self.repl.push("foobar = 2") @@ -443,7 +457,9 @@ def test_updating_namespace_complete(self): self.assertEqual(self.repl.matches_iter.matches, ["foobar"]) def test_file_should_not_appear_in_complete(self): - self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE}) + self.repl = FakeRepl( + {"autocomplete_mode": autocomplete.AutocompleteModes.SIMPLE} + ) self.set_input_line("_") self.assertTrue(self.repl.complete()) self.assertTrue(hasattr(self.repl.matches_iter, "matches")) @@ -451,7 +467,9 @@ def test_file_should_not_appear_in_complete(self): # 4. Parameter names def test_paremeter_name_completion(self): - self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE}) + self.repl = FakeRepl( + {"autocomplete_mode": autocomplete.AutocompleteModes.SIMPLE} + ) self.set_input_line("foo(ab") code = "def foo(abc=1, abd=2, xyz=3):\n\tpass\n" @@ -515,7 +533,9 @@ def test_simple_tab_complete(self): @unittest.skip("disabled while non-simple completion is disabled") def test_substring_tab_complete(self): self.repl.s = "bar" - self.repl.config.autocomplete_mode = autocomplete.FUZZY + self.repl.config.autocomplete_mode = ( + autocomplete.AutocompleteModes.FUZZY + ) self.repl.tab() self.assertEqual(self.repl.s, "foobar") self.repl.tab() @@ -524,7 +544,9 @@ def test_substring_tab_complete(self): @unittest.skip("disabled while non-simple completion is disabled") def test_fuzzy_tab_complete(self): self.repl.s = "br" - self.repl.config.autocomplete_mode = autocomplete.FUZZY + self.repl.config.autocomplete_mode = ( + autocomplete.AutocompleteModes.FUZZY + ) self.repl.tab() self.assertEqual(self.repl.s, "foobar") @@ -561,7 +583,9 @@ def test_back_parameter(self): def test_fuzzy_attribute_tab_complete(self): """Test fuzzy attribute with no text""" self.repl.s = "Foo." - self.repl.config.autocomplete_mode = autocomplete.FUZZY + self.repl.config.autocomplete_mode = ( + autocomplete.AutocompleteModes.FUZZY + ) self.repl.tab() self.assertEqual(self.repl.s, "Foo.foobar") @@ -570,7 +594,9 @@ def test_fuzzy_attribute_tab_complete(self): def test_fuzzy_attribute_tab_complete2(self): """Test fuzzy attribute with some text""" self.repl.s = "Foo.br" - self.repl.config.autocomplete_mode = autocomplete.FUZZY + self.repl.config.autocomplete_mode = ( + autocomplete.AutocompleteModes.FUZZY + ) self.repl.tab() self.assertEqual(self.repl.s, "Foo.foobar") @@ -588,7 +614,9 @@ def test_simple_expand(self): @unittest.skip("disabled while non-simple completion is disabled") def test_substring_expand_forward(self): - self.repl.config.autocomplete_mode = autocomplete.SUBSTRING + self.repl.config.autocomplete_mode = ( + autocomplete.AutocompleteModes.SUBSTRING + ) self.repl.s = "ba" self.repl.tab() self.assertEqual(self.repl.s, "bar") From 79e60136e13392c5f8f750ec5e7cc6a182da0a4a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 30 Dec 2020 18:49:14 +0100 Subject: [PATCH 1114/1650] Use find_all_modules and reload instead of find_iterator --- bpython/curtsies.py | 4 ++-- bpython/test/test_importcompletion.py | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 98c064bd2..3e94a71f5 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -13,7 +13,7 @@ from . import args as bpargs from . import translations from .translations import _ -from .importcompletion import find_iterator +from .importcompletion import find_all_modules from .curtsiesfrontend import events as bpythonevents from . import inspection from .repl import extract_exit_value @@ -113,7 +113,7 @@ def mainloop(self, interactive=True, paste=None): # do a display before waiting for first event self.process_event_and_paint(None) inputs = combined_events(self.input_generator) - for unused in find_iterator: + for unused in find_all_modules(): e = inputs.send(0) if e is not None: self.process_event_and_paint(e) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index af710b2ef..e585b8a4c 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -31,16 +31,10 @@ def test_package_completion(self): class TestRealComplete(unittest.TestCase): @classmethod def setUpClass(cls): - for _ in importcompletion.find_iterator: - pass + importcompletion.reload() __import__("sys") __import__("os") - @classmethod - def tearDownClass(cls): - importcompletion.find_iterator = importcompletion.find_all_modules() - importcompletion.modules = set() - def test_from_attribute(self): self.assertSetEqual( importcompletion.complete(19, "from sys import arg"), {"argv"} From cea4db1c915484d5aa793aa3b06bf197d88384f0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 30 Dec 2020 19:00:33 +0100 Subject: [PATCH 1115/1650] Use find_coroutine instead of find_all_modules --- bpython/curtsies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 3e94a71f5..06ae85f94 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -13,7 +13,7 @@ from . import args as bpargs from . import translations from .translations import _ -from .importcompletion import find_all_modules +from .importcompletion import find_coroutine from .curtsiesfrontend import events as bpythonevents from . import inspection from .repl import extract_exit_value @@ -113,7 +113,7 @@ def mainloop(self, interactive=True, paste=None): # do a display before waiting for first event self.process_event_and_paint(None) inputs = combined_events(self.input_generator) - for unused in find_all_modules(): + while find_coroutine(): e = inputs.send(0) if e is not None: self.process_event_and_paint(e) From 400d7e2203a13eb8f4b118c4d8c32dada8f6e077 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 30 Dec 2020 19:13:56 +0100 Subject: [PATCH 1116/1650] Remove unused get_completer_bpython --- bpython/autocomplete.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 5a273424c..24a207369 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -672,11 +672,6 @@ def get_default_completer(mode=AutocompleteModes.SIMPLE): ) -def get_completer_bpython(cursor_offset, line, **kwargs): - """""" - return get_completer(get_default_completer(), cursor_offset, line, **kwargs) - - def _callable_postfix(value, word): """rlcompleter's _callable_postfix done right.""" if callable(value): From ec79bd833d4cfa24bdb8f0c506e8035784e395bb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 30 Dec 2020 21:46:54 +0100 Subject: [PATCH 1117/1650] Remove global state of bpython.importcompletion Repls now have a ModuleGartherer instance that performs the job. --- bpython/autocomplete.py | 12 +- bpython/cli.py | 5 +- bpython/curtsies.py | 3 +- bpython/importcompletion.py | 378 +++++++++++------------ bpython/repl.py | 4 +- bpython/simplerepl.py | 9 +- bpython/test/test_import_not_cyclical.py | 10 +- bpython/test/test_importcompletion.py | 26 +- bpython/urwid.py | 3 +- 9 files changed, 225 insertions(+), 225 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 24a207369..6b6698b13 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -1,6 +1,7 @@ # The MIT License # # Copyright (c) 2009-2015 the bpython authors. +# Copyright (c) 2015-2020 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -34,7 +35,6 @@ from enum import Enum from . import inspection -from . import importcompletion from . import line as lineparts from .line import LinePart from .lazyre import LazyReCompile @@ -285,8 +285,12 @@ def matches(self, cursor_offset, line, **kwargs): class ImportCompletion(BaseCompletionType): + def __init__(self, module_gatherer, mode=AutocompleteModes.SIMPLE): + super().__init__(False, mode) + self.module_gatherer = module_gatherer + def matches(self, cursor_offset, line, **kwargs): - return importcompletion.complete(cursor_offset, line) + return self.module_gatherer.complete(cursor_offset, line) def locate(self, current_offset, line): return lineparts.current_word(current_offset, line) @@ -656,10 +660,10 @@ def get_completer(completers, cursor_offset, line, **kwargs): return [], None -def get_default_completer(mode=AutocompleteModes.SIMPLE): +def get_default_completer(mode=AutocompleteModes.SIMPLE, module_gatherer=None): return ( DictKeyCompletion(mode=mode), - ImportCompletion(mode=mode), + ImportCompletion(module_gatherer, mode=mode), FilenameCompletion(mode=mode), MagicMethodCompletion(mode=mode), MultilineJediCompletion(mode=mode), diff --git a/bpython/cli.py b/bpython/cli.py index 218949aab..43180bb0b 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -66,9 +66,6 @@ from pygments.token import Token from .formatter import BPythonFormatter -# This for completion -from . import importcompletion - # This for config from .config import Struct, getpreferredencoding @@ -1784,7 +1781,7 @@ def idle(caller): sure it happens conveniently.""" global DO_RESIZE - if importcompletion.find_coroutine() or caller.paste_mode: + if caller.module_gatherer.find_coroutine() or caller.paste_mode: caller.scr.nodelay(True) key = caller.scr.getch() caller.scr.nodelay(False) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 06ae85f94..0c0b44789 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -13,7 +13,6 @@ from . import args as bpargs from . import translations from .translations import _ -from .importcompletion import find_coroutine from .curtsiesfrontend import events as bpythonevents from . import inspection from .repl import extract_exit_value @@ -113,7 +112,7 @@ def mainloop(self, interactive=True, paste=None): # do a display before waiting for first event self.process_event_and_paint(None) inputs = combined_events(self.input_generator) - while find_coroutine(): + while self.module_gatherer.find_coroutine(): e = inputs.send(0) if e is not None: self.process_event_and_paint(e) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index a753932fb..920e4234a 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -1,6 +1,7 @@ # The MIT License # # Copyright (c) 2009-2011 Andreas Stuehrk +# Copyright (c) 2020 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -21,10 +22,10 @@ # THE SOFTWARE. import fnmatch +import importlib.machinery import os import sys import warnings -import importlib.machinery from .line import ( current_word, @@ -35,204 +36,201 @@ SUFFIXES = importlib.machinery.all_suffixes() -# The cached list of all known modules -modules = set() -# List of stored paths to compare against so that real paths are not repeated -# handles symlinks not mount points -paths = set() -# Patterns to skip -# TODO: This skiplist should be configurable. -skiplist = ( - # version tracking - ".git", ".svn", ".hg" - # XDG - ".config", ".local", ".share", - # nodejs - "node_modules", - # PlayOnLinux - "PlayOnLinux's virtual drives", - # wine - "dosdevices", -) -fully_loaded = False - - -def module_matches(cw, prefix=""): - """Modules names to replace cw with""" - full = f"{prefix}.{cw}" if prefix else cw - matches = ( - name - for name in modules - if (name.startswith(full) and name.find(".", len(full)) == -1) - ) - if prefix: - return {match[len(prefix) + 1 :] for match in matches} - else: - return set(matches) - - -def attr_matches(cw, prefix="", only_modules=False): - """Attributes to replace name with""" - full = f"{prefix}.{cw}" if prefix else cw - module_name, _, name_after_dot = full.rpartition(".") - if module_name not in sys.modules: - return set() - module = sys.modules[module_name] - if only_modules: - matches = { + +class ModuleGatherer: + def __init__(self, path=None): + # The cached list of all known modules + self.modules = set() + # List of stored paths to compare against so that real paths are not repeated + # handles symlinks not mount points + self.paths = set() + # Patterns to skip + # TODO: This skiplist should be configurable. + self.skiplist = ( + # version tracking + ".git", + ".svn", + ".hg" + # XDG + ".config", + ".local", + ".share", + # nodejs + "node_modules", + # PlayOnLinux + "PlayOnLinux's virtual drives", + # wine + "dosdevices", + # cache + "__pycache__", + ) + self.fully_loaded = False + self.find_iterator = self.find_all_modules(path) + + def module_matches(self, cw, prefix=""): + """Modules names to replace cw with""" + + full = f"{prefix}.{cw}" if prefix else cw + matches = ( name - for name in dir(module) - if name.startswith(name_after_dot) - and f"{module_name}.{name}" in sys.modules - } - else: - matches = { - name for name in dir(module) if name.startswith(name_after_dot) - } - module_part, _, _ = cw.rpartition(".") - if module_part: - matches = {f"{module_part}.{m}" for m in matches} - - return matches - - -def module_attr_matches(name): - """Only attributes which are modules to replace name with""" - return attr_matches(name, prefix="", only_modules=True) - - -def complete(cursor_offset, line): - """Construct a full list of possibly completions for imports.""" - tokens = line.split() - if "from" not in tokens and "import" not in tokens: - return None - - result = current_word(cursor_offset, line) - if result is None: - return None - - from_import_from = current_from_import_from(cursor_offset, line) - if from_import_from is not None: - import_import = current_from_import_import(cursor_offset, line) - if import_import is not None: - # `from a import ` completion - matches = module_matches(import_import[2], from_import_from[2]) - matches.update(attr_matches(import_import[2], from_import_from[2])) + for name in self.modules + if (name.startswith(full) and name.find(".", len(full)) == -1) + ) + if prefix: + return {match[len(prefix) + 1 :] for match in matches} else: - # `from ` completion - matches = module_attr_matches(from_import_from[2]) - matches.update(module_matches(from_import_from[2])) - return matches + return set(matches) + + def attr_matches(self, cw, prefix="", only_modules=False): + """Attributes to replace name with""" + full = f"{prefix}.{cw}" if prefix else cw + module_name, _, name_after_dot = full.rpartition(".") + if module_name not in sys.modules: + return set() + module = sys.modules[module_name] + if only_modules: + matches = { + name + for name in dir(module) + if name.startswith(name_after_dot) + and f"{module_name}.{name}" in sys.modules + } + else: + matches = { + name for name in dir(module) if name.startswith(name_after_dot) + } + module_part, _, _ = cw.rpartition(".") + if module_part: + matches = {f"{module_part}.{m}" for m in matches} - cur_import = current_import(cursor_offset, line) - if cur_import is not None: - # `import ` completion - matches = module_matches(cur_import[2]) - matches.update(module_attr_matches(cur_import[2])) return matches - else: - return None - -def find_modules(path): - """Find all modules (and packages) for a given directory.""" - if not os.path.isdir(path): - # Perhaps a zip file - return - basepath = os.path.basename(path) - if any(fnmatch.fnmatch(basepath, entry) for entry in skiplist): - # Path is on skiplist - return + def module_attr_matches(self, name): + """Only attributes which are modules to replace name with""" + return self.attr_matches(name, prefix="", only_modules=True) + + def complete(self, cursor_offset, line): + """Construct a full list of possibly completions for imports.""" + tokens = line.split() + if "from" not in tokens and "import" not in tokens: + return None + + result = current_word(cursor_offset, line) + if result is None: + return None + + from_import_from = current_from_import_from(cursor_offset, line) + if from_import_from is not None: + import_import = current_from_import_import(cursor_offset, line) + if import_import is not None: + # `from a import ` completion + matches = self.module_matches( + import_import[2], from_import_from[2] + ) + matches.update( + self.attr_matches(import_import[2], from_import_from[2]) + ) + else: + # `from ` completion + matches = self.module_attr_matches(from_import_from[2]) + matches.update(self.module_matches(from_import_from[2])) + return matches + + cur_import = current_import(cursor_offset, line) + if cur_import is not None: + # `import ` completion + matches = self.module_matches(cur_import[2]) + matches.update(self.module_attr_matches(cur_import[2])) + return matches + else: + return None + + def find_modules(self, path): + """Find all modules (and packages) for a given directory.""" + if not os.path.isdir(path): + # Perhaps a zip file + return + basepath = os.path.basename(path) + if any(fnmatch.fnmatch(basepath, entry) for entry in self.skiplist): + # Path is on skiplist + return - try: - filenames = os.listdir(path) - except OSError: - filenames = [] + try: + filenames = os.listdir(path) + except OSError: + filenames = [] - finder = importlib.machinery.FileFinder(path) + finder = importlib.machinery.FileFinder(path) - for name in filenames: - if any(fnmatch.fnmatch(name, entry) for entry in skiplist): - # Path is on skiplist - continue - elif not any(name.endswith(suffix) for suffix in SUFFIXES): - # Possibly a package - if "." in name: + for name in filenames: + if any(fnmatch.fnmatch(name, entry) for entry in self.skiplist): + # Path is on skiplist continue - elif os.path.isdir(os.path.join(path, name)): - # Unfortunately, CPython just crashes if there is a directory - # which ends with a python extension, so work around. - continue - for suffix in SUFFIXES: - if name.endswith(suffix): - name = name[: -len(suffix)] - break - if name == "badsyntax_pep3120": - # Workaround for issue #166 - continue - try: - is_package = False - with warnings.catch_warnings(): - warnings.simplefilter("ignore", ImportWarning) - spec = finder.find_spec(name) - if spec is None: + elif not any(name.endswith(suffix) for suffix in SUFFIXES): + # Possibly a package + if "." in name: continue - if spec.submodule_search_locations is not None: - pathname = spec.submodule_search_locations[0] - is_package = True - else: - pathname = spec.origin - except (ImportError, OSError, SyntaxError): - continue - except UnicodeEncodeError: - # Happens with Python 3 when there is a filename in some - # invalid encoding - continue - else: - if is_package: - path_real = os.path.realpath(pathname) - if path_real not in paths: - paths.add(path_real) - for subname in find_modules(pathname): - if subname != "__init__": - yield f"{name}.{subname}" - yield name - - -def find_all_modules(path=None): - """Return a list with all modules in `path`, which should be a list of - directory names. If path is not given, sys.path will be used.""" - if path is None: - modules.update(sys.builtin_module_names) - path = sys.path - - for p in path: - if not p: - p = os.curdir - for module in find_modules(p): - modules.add(module) - yield - - -def find_coroutine(): - global fully_loaded - - if fully_loaded: - return None - - try: - next(find_iterator) - except StopIteration: - fully_loaded = True - - return True - - -def reload(): - """Refresh the list of known modules.""" - modules.clear() - for _ in find_all_modules(): - pass + elif os.path.isdir(os.path.join(path, name)): + # Unfortunately, CPython just crashes if there is a directory + # which ends with a python extension, so work around. + continue + for suffix in SUFFIXES: + if name.endswith(suffix): + name = name[: -len(suffix)] + break + if name == "badsyntax_pep3120": + # Workaround for issue #166 + continue + try: + is_package = False + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + spec = finder.find_spec(name) + if spec is None: + continue + if spec.submodule_search_locations is not None: + pathname = spec.submodule_search_locations[0] + is_package = True + else: + pathname = spec.origin + except (ImportError, OSError, SyntaxError): + continue + except UnicodeEncodeError: + # Happens with Python 3 when there is a filename in some + # invalid encoding + continue + else: + if is_package: + path_real = os.path.realpath(pathname) + if path_real not in self.paths: + self.paths.add(path_real) + for subname in self.find_modules(pathname): + if subname != "__init__": + yield f"{name}.{subname}" + yield name + + def find_all_modules(self, path=None): + """Return a list with all modules in `path`, which should be a list of + directory names. If path is not given, sys.path will be used.""" + + if path is None: + self.modules.update(sys.builtin_module_names) + path = sys.path + + for p in path: + if not p: + p = os.curdir + for module in self.find_modules(p): + self.modules.add(module) + yield + + def find_coroutine(self): + if self.fully_loaded: + return None + try: + next(self.find_iterator) + except StopIteration: + self.fully_loaded = True -find_iterator = find_all_modules() + return True diff --git a/bpython/repl.py b/bpython/repl.py index 9b23221ad..35a48c16a 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -48,6 +48,7 @@ from .paste import PasteHelper, PastePinnwand, PasteFailed from .patch_linecache import filename_for_console_input from .translations import _, ngettext +from .importcompletion import ModuleGatherer class RuntimeTimer: @@ -443,8 +444,9 @@ def __init__(self, interp, config): except OSError: pass + self.module_gatherer = ModuleGatherer() self.completers = autocomplete.get_default_completer( - config.autocomplete_mode + config.autocomplete_mode, self.module_gatherer ) if self.config.pastebin_helper: self.paster = PasteHelper(self.config.pastebin_helper) diff --git a/bpython/simplerepl.py b/bpython/simplerepl.py index 9ec6bea86..24f7380e8 100644 --- a/bpython/simplerepl.py +++ b/bpython/simplerepl.py @@ -27,10 +27,10 @@ import time import logging -from .curtsiesfrontend.repl import BaseRepl -from .curtsiesfrontend import events as bpythonevents from . import translations -from . import importcompletion +from .curtsiesfrontend import events as bpythonevents +from .curtsiesfrontend.repl import BaseRepl +from .importcompletion import ModuleGatherer from curtsies.configfile_keynames import keymap as key_dispatch @@ -116,7 +116,8 @@ def get_input(self): def main(args=None, locals_=None, banner=None): translations.init() - while importcompletion.find_coroutine(): + module_gatherer = ModuleGatherer() + while module_gatherer.find_coroutine(): pass with SimpleRepl() as r: r.width = 50 diff --git a/bpython/test/test_import_not_cyclical.py b/bpython/test/test_import_not_cyclical.py index a1001365c..ea1a28b1c 100644 --- a/bpython/test/test_import_not_cyclical.py +++ b/bpython/test/test_import_not_cyclical.py @@ -2,7 +2,7 @@ import tempfile from bpython.test import unittest -from bpython.importcompletion import find_modules +from bpython.importcompletion import ModuleGatherer class TestAvoidSymbolicLinks(unittest.TestCase): @@ -62,9 +62,11 @@ def setUp(self): True, ) - self.modules = list( - find_modules(os.path.abspath(import_test_folder)) + self.module_gatherer = ModuleGatherer( + [os.path.abspath(import_test_folder)] ) + while self.module_gatherer.find_coroutine(): + pass self.filepaths = [ "Left.toRight.toLeft", "Left.toRight", @@ -79,7 +81,7 @@ def setUp(self): ] def test_simple_symbolic_link_loop(self): - for thing in self.modules: + for thing in self.module_gatherer.modules: self.assertTrue(thing in self.filepaths) if thing == "Left.toRight.toLeft": self.filepaths.remove("Right.toLeft") diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index e585b8a4c..d7aac54ce 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -1,11 +1,11 @@ -from bpython import importcompletion +from bpython.importcompletion import ModuleGatherer from bpython.test import unittest class TestSimpleComplete(unittest.TestCase): def setUp(self): - self.original_modules = importcompletion.modules - importcompletion.modules = [ + self.module_gatherer = ModuleGatherer() + self.module_gatherer.modules = [ "zzabc", "zzabd", "zzefg", @@ -13,39 +13,37 @@ def setUp(self): "zzabc.f", ] - def tearDown(self): - importcompletion.modules = self.original_modules - def test_simple_completion(self): self.assertSetEqual( - importcompletion.complete(10, "import zza"), {"zzabc", "zzabd"} + self.module_gatherer.complete(10, "import zza"), {"zzabc", "zzabd"} ) def test_package_completion(self): self.assertSetEqual( - importcompletion.complete(13, "import zzabc."), + self.module_gatherer.complete(13, "import zzabc."), {"zzabc.e", "zzabc.f"}, ) class TestRealComplete(unittest.TestCase): - @classmethod - def setUpClass(cls): - importcompletion.reload() + def setUp(self): + self.module_gatherer = ModuleGatherer() + while self.module_gatherer.find_coroutine(): + pass __import__("sys") __import__("os") def test_from_attribute(self): self.assertSetEqual( - importcompletion.complete(19, "from sys import arg"), {"argv"} + self.module_gatherer.complete(19, "from sys import arg"), {"argv"} ) def test_from_attr_module(self): self.assertSetEqual( - importcompletion.complete(9, "from os.p"), {"os.path"} + self.module_gatherer.complete(9, "from os.p"), {"os.path"} ) def test_from_package(self): self.assertSetEqual( - importcompletion.complete(17, "from xml import d"), {"dom"} + self.module_gatherer.complete(17, "from xml import d"), {"dom"} ) diff --git a/bpython/urwid.py b/bpython/urwid.py index d9e0a388c..1f63b1370 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -39,7 +39,6 @@ from . import args as bpargs, repl, translations from .formatter import theme_map -from .importcompletion import find_coroutine from .translations import _ from .keys import urwid_key_dispatch as key_dispatch @@ -1344,7 +1343,7 @@ def start(main_loop, user_data): # This bypasses main_loop.set_alarm_in because we must *not* # hit the draw_screen call (it's unnecessary and slow). def run_find_coroutine(): - if find_coroutine(): + if myrepl.module_gatherer.find_coroutine(): main_loop.event_loop.alarm(0, run_find_coroutine) run_find_coroutine() From cf5763eca1ca18c6b4b8c580a53183a4c3f8248a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 30 Dec 2020 22:22:02 +0100 Subject: [PATCH 1118/1650] Make importcompletion skiplist configurable (fixes #849) --- bpython/config.py | 23 +++++++++++++++++++++ bpython/importcompletion.py | 22 ++------------------ bpython/repl.py | 4 +++- doc/sphinx/source/configuration-options.rst | 6 ++++++ 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 59b12c44e..68e700915 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -70,6 +70,26 @@ def loadini(struct, configfile): "default_autoreload": False, "editor": os.environ.get("VISUAL", os.environ.get("EDITOR", "vi")), "flush_output": True, + "import_completion_skiplist": ":".join( + ( + # version tracking + ".git", + ".svn", + ".hg" + # XDG + ".config", + ".local", + ".share", + # nodejs + "node_modules", + # PlayOnLinux + "PlayOnLinux's virtual drives", + # wine + "dosdevices", + # Python byte code cache + "__pycache__", + ) + ), "highlight_show_source": True, "hist_duplicates": True, "hist_file": "~/.pythonhist", @@ -188,6 +208,9 @@ def get_key_no_doublebind(command): struct.default_autoreload = config.getboolean( "general", "default_autoreload" ) + struct.import_completion_skiplist = config.get( + "general", "import_completion_skiplist" + ).split(":") struct.pastebin_key = get_key_no_doublebind("pastebin") struct.copy_clipboard_key = get_key_no_doublebind("copy_clipboard") diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 920e4234a..aca7e6602 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -38,32 +38,14 @@ class ModuleGatherer: - def __init__(self, path=None): + def __init__(self, path=None, skiplist=None): # The cached list of all known modules self.modules = set() # List of stored paths to compare against so that real paths are not repeated # handles symlinks not mount points self.paths = set() # Patterns to skip - # TODO: This skiplist should be configurable. - self.skiplist = ( - # version tracking - ".git", - ".svn", - ".hg" - # XDG - ".config", - ".local", - ".share", - # nodejs - "node_modules", - # PlayOnLinux - "PlayOnLinux's virtual drives", - # wine - "dosdevices", - # cache - "__pycache__", - ) + self.skiplist = skiplist if skiplist is not None else tuple() self.fully_loaded = False self.find_iterator = self.find_all_modules(path) diff --git a/bpython/repl.py b/bpython/repl.py index 35a48c16a..5fb75f04a 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -444,7 +444,9 @@ def __init__(self, interp, config): except OSError: pass - self.module_gatherer = ModuleGatherer() + self.module_gatherer = ModuleGatherer( + skiplist=self.config.import_completion_skiplist + ) self.completers = autocomplete.get_default_completer( config.autocomplete_mode, self.module_gatherer ) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index b1eeb068a..4bc688207 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -174,6 +174,12 @@ Whether to use Unicode characters to draw boxes. .. versionadded:: 0.14 +import_completion_skiplist +^^^^^^^^^^^^^^^^^^^^^^^^^^ +A `:`-seperated list of patterns to skip when processing modules for import completion. + +.. versionadded:: 0.21 + Keyboard -------- This section refers to the ``[keyboard]`` section in your From bd277b645417e82f7740ef6a1824676611748109 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 30 Dec 2020 23:14:18 +0100 Subject: [PATCH 1119/1650] Import unittest and mock directly --- bpython/test/__init__.py | 12 +++--------- bpython/test/test_args.py | 5 +++-- bpython/test/test_autocomplete.py | 2 +- bpython/test/test_config.py | 2 +- bpython/test/test_crashers.py | 3 ++- bpython/test/test_curtsies.py | 5 +++-- bpython/test/test_curtsies_coderunner.py | 3 ++- bpython/test/test_curtsies_painting.py | 4 ++-- bpython/test/test_curtsies_repl.py | 7 ++++--- bpython/test/test_filewatch.py | 3 ++- bpython/test/test_history.py | 3 +-- bpython/test/test_import_not_cyclical.py | 2 +- bpython/test/test_importcompletion.py | 3 ++- bpython/test/test_inspection.py | 2 +- bpython/test/test_interpreter.py | 3 ++- bpython/test/test_keys.py | 3 ++- bpython/test/test_line_properties.py | 2 +- bpython/test/test_manual_readline.py | 3 ++- bpython/test/test_preprocess.py | 12 ++++++------ bpython/test/test_repl.py | 9 ++++++--- bpython/test/test_simpleeval.py | 2 +- 21 files changed, 48 insertions(+), 42 deletions(-) diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index 896f856fe..ca62a204d 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -1,9 +1,8 @@ import unittest -import builtins -from unittest import mock +import unittest.mock +import os from bpython.translations import init -import os class FixLanguageTestCase(unittest.TestCase): @@ -12,14 +11,9 @@ def setUpClass(cls): init(languages=["en"]) -class MagicIterMock(mock.MagicMock): +class MagicIterMock(unittest.mock.MagicMock): __next__ = mock.Mock(return_value=None) -def builtin_target(obj): - """Returns mock target string of a builtin""" - return f"{builtins.__name__}.{obj.__name__}" - - TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index e71a9ddfd..2115bc088 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -2,10 +2,11 @@ import subprocess import sys import tempfile -from textwrap import dedent +import unittest +from textwrap import dedent from bpython import args -from bpython.test import FixLanguageTestCase as TestCase, unittest +from bpython.test import FixLanguageTestCase as TestCase class TestExecArgs(unittest.TestCase): diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 852a6ef7b..5787744c0 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -2,6 +2,7 @@ import keyword import unittest from collections import namedtuple +from unittest import mock try: import jedi @@ -11,7 +12,6 @@ has_jedi = False from bpython import autocomplete -from bpython.test import mock glob_function = "glob.iglob" diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index a4d081da9..76db5d293 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -1,8 +1,8 @@ import os import tempfile import textwrap +import unittest -from bpython.test import unittest from bpython import config TEST_THEME_PATH = os.path.join(os.path.dirname(__file__), "test.theme") diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 7a92f4f87..3315f418e 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -5,8 +5,9 @@ import sys import termios import textwrap +import unittest -from bpython.test import unittest, TEST_CONFIG +from bpython.test import TEST_CONFIG from bpython.config import getpreferredencoding try: diff --git a/bpython/test/test_curtsies.py b/bpython/test/test_curtsies.py index d27e53dac..fde2b1037 100644 --- a/bpython/test/test_curtsies.py +++ b/bpython/test/test_curtsies.py @@ -1,7 +1,8 @@ -from collections import namedtuple +import unittest +from collections import namedtuple from bpython.curtsies import combined_events -from bpython.test import FixLanguageTestCase as TestCase, unittest +from bpython.test import FixLanguageTestCase as TestCase import curtsies.events diff --git a/bpython/test/test_curtsies_coderunner.py b/bpython/test/test_curtsies_coderunner.py index 3a677b561..bb5cec423 100644 --- a/bpython/test/test_curtsies_coderunner.py +++ b/bpython/test/test_curtsies_coderunner.py @@ -1,6 +1,7 @@ import sys +import unittest -from bpython.test import mock, unittest +from unittest import mock from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index a5d958a05..fb82627ee 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -2,13 +2,13 @@ import pydoc import string import sys -from contextlib import contextmanager +from contextlib import contextmanager from curtsies.formatstringarray import FormatStringTest, fsarray from curtsies.fmtfuncs import cyan, bold, green, yellow, on_magenta, red +from unittest import mock from bpython.curtsiesfrontend.events import RefreshRequestEvent -from bpython.test import mock from bpython import config, inspection from bpython.curtsiesfrontend.repl import BaseRepl from bpython.curtsiesfrontend import replpainter diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 824c166e0..4a7544d1b 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -3,8 +3,11 @@ import sys import tempfile import io -from functools import partial +import unittest + from contextlib import contextmanager +from functools import partial +from unittest import mock from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter @@ -16,8 +19,6 @@ from bpython.test import ( FixLanguageTestCase as TestCase, MagicIterMock, - mock, - unittest, TEST_CONFIG, ) diff --git a/bpython/test/test_filewatch.py b/bpython/test/test_filewatch.py index 9f2790b28..67b29f943 100644 --- a/bpython/test/test_filewatch.py +++ b/bpython/test/test_filewatch.py @@ -1,4 +1,5 @@ import os +import unittest try: import watchdog @@ -8,7 +9,7 @@ except ImportError: has_watchdog = False -from bpython.test import mock, unittest +from unittest import mock @unittest.skipUnless(has_watchdog, "watchdog required") diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index 6e8756a5c..71d7847ce 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -1,9 +1,8 @@ import os - +import unittest from bpython.config import getpreferredencoding from bpython.history import History -from bpython.test import unittest class TestHistory(unittest.TestCase): diff --git a/bpython/test/test_import_not_cyclical.py b/bpython/test/test_import_not_cyclical.py index ea1a28b1c..25633182f 100644 --- a/bpython/test/test_import_not_cyclical.py +++ b/bpython/test/test_import_not_cyclical.py @@ -1,7 +1,7 @@ import os import tempfile +import unittest -from bpython.test import unittest from bpython.importcompletion import ModuleGatherer diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index d7aac54ce..59f83e16a 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -1,5 +1,6 @@ +import unittest + from bpython.importcompletion import ModuleGatherer -from bpython.test import unittest class TestSimpleComplete(unittest.TestCase): diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 6bf6e7793..885c0dbe1 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -1,7 +1,7 @@ import os +import unittest from bpython import inspection -from bpython.test import unittest from bpython.test.fodder import encoding_ascii from bpython.test.fodder import encoding_latin1 from bpython.test.fodder import encoding_utf8 diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index afa71aa79..9b93672c0 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -1,10 +1,11 @@ import sys import re +import unittest from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain +from unittest import mock from bpython.curtsiesfrontend import interpreter -from bpython.test import mock, unittest pypy = "PyPy" in sys.version diff --git a/bpython/test/test_keys.py b/bpython/test/test_keys.py index 671182bc3..23e8798cc 100644 --- a/bpython/test/test_keys.py +++ b/bpython/test/test_keys.py @@ -1,5 +1,6 @@ +import unittest + from bpython import keys -from bpython.test import unittest class TestCLIKeys(unittest.TestCase): diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index f139b3103..fe1b0813e 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -1,6 +1,6 @@ import re +import unittest -from bpython.test import unittest from bpython.line import ( current_word, current_dict_key, diff --git a/bpython/test/test_manual_readline.py b/bpython/test/test_manual_readline.py index 7b0936ae1..445e78b30 100644 --- a/bpython/test/test_manual_readline.py +++ b/bpython/test/test_manual_readline.py @@ -1,3 +1,5 @@ +import unittest + from bpython.curtsiesfrontend.manual_readline import ( left_arrow, right_arrow, @@ -16,7 +18,6 @@ UnconfiguredEdits, delete_word_from_cursor_back, ) -from bpython.test import unittest class TestManualReadline(unittest.TestCase): diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index 143dfd3e7..03e9a3b8e 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -1,16 +1,16 @@ -from code import compile_command as compiler -from functools import partial import difflib import inspect import re +import unittest + +from code import compile_command as compiler +from functools import partial from bpython.curtsiesfrontend.interpreter import code_finished_will_parse from bpython.curtsiesfrontend.preprocess import preprocess -from bpython.test import unittest from bpython.test.fodder import original, processed -skip = unittest.skip preproc = partial(preprocess, compiler=compiler) @@ -85,14 +85,14 @@ def test_empty_line_within_class(self): def test_blank_lines_in_for_loop(self): self.assertIndented("blank_lines_in_for_loop") - @skip( + @unittest.skip( "More advanced technique required: need to try compiling and " "backtracking" ) def test_blank_line_in_try_catch(self): self.assertIndented("blank_line_in_try_catch") - @skip( + @unittest.skip( "More advanced technique required: need to try compiling and " "backtracking" ) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 7182416e1..1c56f8444 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,4 +1,3 @@ -from itertools import islice import collections import inspect import os @@ -6,10 +5,14 @@ import socket import sys import tempfile +import unittest + +from itertools import islice +from unittest import mock from bpython import config, repl, cli, autocomplete -from bpython.test import MagicIterMock, mock, FixLanguageTestCase as TestCase -from bpython.test import unittest, TEST_CONFIG +from bpython.test import MagicIterMock, FixLanguageTestCase as TestCase +from bpython.test import TEST_CONFIG pypy = "PyPy" in sys.version diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 2865ed68f..1d1a3f1a3 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -1,13 +1,13 @@ import ast import numbers import sys +import unittest from bpython.simpleeval import ( simple_eval, evaluate_current_expression, EvaluationError, ) -from bpython.test import unittest class TestSimpleEval(unittest.TestCase): From 18fc6ff2082b9297b3e4f8a09132a1f33537a043 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 30 Dec 2020 23:20:40 +0100 Subject: [PATCH 1120/1650] Fix use of mock --- bpython/test/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index ca62a204d..cb9caef5f 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -13,7 +13,7 @@ def setUpClass(cls): class MagicIterMock(unittest.mock.MagicMock): - __next__ = mock.Mock(return_value=None) + __next__ = unittest.mock.Mock(return_value=None) TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") From 7fca6d79001c3a035f206a9807b54aee46af3fcd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 30 Dec 2020 23:25:31 +0100 Subject: [PATCH 1121/1650] Consolidate importcompletion tests --- bpython/test/test_import_not_cyclical.py | 97 ------------------------ bpython/test/test_importcompletion.py | 90 ++++++++++++++++++++++ 2 files changed, 90 insertions(+), 97 deletions(-) delete mode 100644 bpython/test/test_import_not_cyclical.py diff --git a/bpython/test/test_import_not_cyclical.py b/bpython/test/test_import_not_cyclical.py deleted file mode 100644 index 25633182f..000000000 --- a/bpython/test/test_import_not_cyclical.py +++ /dev/null @@ -1,97 +0,0 @@ -import os -import tempfile -import unittest - -from bpython.importcompletion import ModuleGatherer - - -class TestAvoidSymbolicLinks(unittest.TestCase): - def setUp(self): - with tempfile.TemporaryDirectory() as import_test_folder: - os.mkdir(os.path.join(import_test_folder, "Level0")) - os.mkdir(os.path.join(import_test_folder, "Right")) - os.mkdir(os.path.join(import_test_folder, "Left")) - - current_path = os.path.join(import_test_folder, "Level0") - with open( - os.path.join(current_path, "__init__.py"), "x" - ) as init_file: - pass - - current_path = os.path.join(current_path, "Level1") - os.mkdir(current_path) - with open( - os.path.join(current_path, "__init__.py"), "x" - ) as init_file: - pass - - current_path = os.path.join(current_path, "Level2") - os.mkdir(current_path) - with open( - os.path.join(current_path, "__init__.py"), "x" - ) as init_file: - pass - - os.symlink( - os.path.join(import_test_folder, "Level0/Level1"), - os.path.join(current_path, "Level3"), - True, - ) - - current_path = os.path.join(import_test_folder, "Right") - with open( - os.path.join(current_path, "__init__.py"), "x" - ) as init_file: - pass - - os.symlink( - os.path.join(import_test_folder, "Left"), - os.path.join(current_path, "toLeft"), - True, - ) - - current_path = os.path.join(import_test_folder, "Left") - with open( - os.path.join(current_path, "__init__.py"), "x" - ) as init_file: - pass - - os.symlink( - os.path.join(import_test_folder, "Right"), - os.path.join(current_path, "toRight"), - True, - ) - - self.module_gatherer = ModuleGatherer( - [os.path.abspath(import_test_folder)] - ) - while self.module_gatherer.find_coroutine(): - pass - self.filepaths = [ - "Left.toRight.toLeft", - "Left.toRight", - "Left", - "Level0.Level1.Level2.Level3", - "Level0.Level1.Level2", - "Level0.Level1", - "Level0", - "Right", - "Right.toLeft", - "Right.toLeft.toRight", - ] - - def test_simple_symbolic_link_loop(self): - for thing in self.module_gatherer.modules: - self.assertTrue(thing in self.filepaths) - if thing == "Left.toRight.toLeft": - self.filepaths.remove("Right.toLeft") - self.filepaths.remove("Right.toLeft.toRight") - if thing == "Right.toLeft.toRight": - self.filepaths.remove("Left.toRight.toLeft") - self.filepaths.remove("Left.toRight") - self.filepaths.remove(thing) - self.assertFalse(self.filepaths) - - -if __name__ == "__main__": - unittest.main() diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 59f83e16a..00700aadb 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -1,3 +1,5 @@ +import os +import tempfile import unittest from bpython.importcompletion import ModuleGatherer @@ -48,3 +50,91 @@ def test_from_package(self): self.assertSetEqual( self.module_gatherer.complete(17, "from xml import d"), {"dom"} ) + + +class TestAvoidSymbolicLinks(unittest.TestCase): + def setUp(self): + with tempfile.TemporaryDirectory() as import_test_folder: + os.mkdir(os.path.join(import_test_folder, "Level0")) + os.mkdir(os.path.join(import_test_folder, "Right")) + os.mkdir(os.path.join(import_test_folder, "Left")) + + current_path = os.path.join(import_test_folder, "Level0") + with open( + os.path.join(current_path, "__init__.py"), "x" + ) as init_file: + pass + + current_path = os.path.join(current_path, "Level1") + os.mkdir(current_path) + with open( + os.path.join(current_path, "__init__.py"), "x" + ) as init_file: + pass + + current_path = os.path.join(current_path, "Level2") + os.mkdir(current_path) + with open( + os.path.join(current_path, "__init__.py"), "x" + ) as init_file: + pass + + os.symlink( + os.path.join(import_test_folder, "Level0/Level1"), + os.path.join(current_path, "Level3"), + True, + ) + + current_path = os.path.join(import_test_folder, "Right") + with open( + os.path.join(current_path, "__init__.py"), "x" + ) as init_file: + pass + + os.symlink( + os.path.join(import_test_folder, "Left"), + os.path.join(current_path, "toLeft"), + True, + ) + + current_path = os.path.join(import_test_folder, "Left") + with open( + os.path.join(current_path, "__init__.py"), "x" + ) as init_file: + pass + + os.symlink( + os.path.join(import_test_folder, "Right"), + os.path.join(current_path, "toRight"), + True, + ) + + self.module_gatherer = ModuleGatherer( + [os.path.abspath(import_test_folder)] + ) + while self.module_gatherer.find_coroutine(): + pass + self.filepaths = [ + "Left.toRight.toLeft", + "Left.toRight", + "Left", + "Level0.Level1.Level2.Level3", + "Level0.Level1.Level2", + "Level0.Level1", + "Level0", + "Right", + "Right.toLeft", + "Right.toLeft.toRight", + ] + + def test_simple_symbolic_link_loop(self): + for thing in self.module_gatherer.modules: + self.assertTrue(thing in self.filepaths) + if thing == "Left.toRight.toLeft": + self.filepaths.remove("Right.toLeft") + self.filepaths.remove("Right.toLeft.toRight") + if thing == "Right.toLeft.toRight": + self.filepaths.remove("Left.toRight.toLeft") + self.filepaths.remove("Left.toRight") + self.filepaths.remove(thing) + self.assertFalse(self.filepaths) From f2298211a8f410376283604040f62a8153dee073 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 30 Dec 2020 23:29:06 +0100 Subject: [PATCH 1122/1650] Simplify using pathlib --- bpython/test/test_importcompletion.py | 28 +++++++-------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 00700aadb..b21207e11 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -2,6 +2,7 @@ import tempfile import unittest +from pathlib import Path from bpython.importcompletion import ModuleGatherer @@ -60,36 +61,24 @@ def setUp(self): os.mkdir(os.path.join(import_test_folder, "Left")) current_path = os.path.join(import_test_folder, "Level0") - with open( - os.path.join(current_path, "__init__.py"), "x" - ) as init_file: - pass + Path(os.path.join(current_path, "__init__.py")).touch() current_path = os.path.join(current_path, "Level1") os.mkdir(current_path) - with open( - os.path.join(current_path, "__init__.py"), "x" - ) as init_file: - pass + Path(os.path.join(current_path, "__init__.py")).touch() current_path = os.path.join(current_path, "Level2") os.mkdir(current_path) - with open( - os.path.join(current_path, "__init__.py"), "x" - ) as init_file: - pass + Path(os.path.join(current_path, "__init__.py")).touch() os.symlink( - os.path.join(import_test_folder, "Level0/Level1"), + os.path.join(import_test_folder, "Level0", "Level1"), os.path.join(current_path, "Level3"), True, ) current_path = os.path.join(import_test_folder, "Right") - with open( - os.path.join(current_path, "__init__.py"), "x" - ) as init_file: - pass + Path(os.path.join(current_path, "__init__.py")).touch() os.symlink( os.path.join(import_test_folder, "Left"), @@ -98,10 +87,7 @@ def setUp(self): ) current_path = os.path.join(import_test_folder, "Left") - with open( - os.path.join(current_path, "__init__.py"), "x" - ) as init_file: - pass + Path(os.path.join(current_path, "__init__.py")).touch() os.symlink( os.path.join(import_test_folder, "Right"), From 40b300cd1c67a90c10fa0371e05f2b869ff8fe13 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 31 Dec 2020 00:24:30 +0100 Subject: [PATCH 1123/1650] Fix formatting --- CHANGELOG.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0f762ca20..a9da60571 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,35 +5,42 @@ Changelog ---- General information: + * Support for Python 2 has been dropped. New features: + * #643: Provide bpython._version if built from Github tarballs Fixes: + * #857: Replace remaining use of deprecated imp with importlib 0.20.1 ------ Fixes: + * Fix check of key code (fixes #859) 0.20 ---- General information: + * The next release of bpython (0.20) will drop support for Python 2. * Support for Python 3.9 has been added. Support for Python 3.5 has been dropped. New features: + * #802: Provide redo. Thanks to Evan. * #835: Add support for importing namespace packages. Thanks to Thomas Babej. Fixes: + * #622: Provide encoding attribute for FakeOutput. * #806: Prevent symbolic link loops in import completion. Thanks to Etienne Richart. @@ -48,6 +55,7 @@ Fixes: ---- General information: + * The bpython-cli and bpython-urwid rendering backends have been deprecated and will show a warning that they'll be removed in a future release when started. * Usage in combination with Python 2 has been deprecated. This does not mean that @@ -61,6 +69,7 @@ General information: New features: Fixes: + * #765: Display correct signature for decorated functions. Thanks to Benedikt Rascher-Friesenhausen. * #776: Protect get_args from user code exceptions @@ -73,10 +82,12 @@ Support for Python 3.8 has been added. Support for Python 3.4 has been dropped. ---- New features: + * #713 expose globals in bpdb debugging. Thanks to toejough. Fixes: + * Fix file locking on Windows. * Exit gracefully if config file fails to be loaded due to encoding errors. * #744: Fix newline handling. @@ -98,11 +109,13 @@ Fixes: ---- New features: + * #641: Implement Ctrl+O. * Add default_autoreload config option. Thanks to Alex Frieder. Fixes: + * Fix deprecation warnings. * Do not call signal outside of main thread. Thanks to Max Nordlund. @@ -122,9 +135,11 @@ Fixes: ---- New features: + * #466: Improve handling of completion box height. Fixes: + * Fix various spelling mistakes. Thanks to Josh Soref and Simeon Visser. * #601: Fix Python 2 issues on Windows. From c68d210d2f75e9318b1f0605563bcd64f978fb1c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 31 Dec 2020 12:04:48 +0100 Subject: [PATCH 1124/1650] No longer complain about old config files --- bpython/config.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 68e700915..4189993bc 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -155,15 +155,7 @@ def loadini(struct, configfile): fill_config_with_default_values(config, defaults) try: - if not config.read(config_path): - # No config file. If the user has it in the old place then complain - if os.path.isfile(os.path.expanduser("~/.bpython.ini")): - sys.stderr.write( - "Error: It seems that you have a config file at " - "~/.bpython.ini. Please move your config file to " - "%s\n" % default_config_path() - ) - sys.exit(1) + config.read(config_path) except UnicodeDecodeError as e: sys.stderr.write( "Error: Unable to parse config file at '{}' due to an " From 8bc9df74ceb1982c3bfa24ba2805783cdf9c0a06 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 31 Dec 2020 13:19:49 +0100 Subject: [PATCH 1125/1650] Use xdg module instead of custom XDG logic --- README.rst | 5 +++-- bpython/config.py | 4 ++-- requirements.txt | 1 + setup.py | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 2bbb777e7..059e78d77 100644 --- a/README.rst +++ b/README.rst @@ -155,13 +155,14 @@ your config file as **~/.config/bpython/config** (i.e. Dependencies ============ * Pygments -* requests * curtsies >= 0.3.3 * greenlet +* requests +* xdg * Sphinx >= 1.5 (optional, for the documentation) * babel (optional, for internationalization) -* watchdog (optional, for monitoring imported modules for changes) * jedi (optional, for experimental multiline completion) +* watchdog (optional, for monitoring imported modules for changes) bpython-urwid ------------- diff --git a/bpython/config.py b/bpython/config.py index 4189993bc..08eeec920 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -3,6 +3,7 @@ import locale from itertools import chain from configparser import ConfigParser +from xdg import BaseDirectory from .autocomplete import AutocompleteModes @@ -34,8 +35,7 @@ def supports_box_chars(): def get_config_home(): """Returns the base directory for bpython's configuration files.""" - xdg_config_home = os.environ.get("XDG_CONFIG_HOME", "~/.config") - return os.path.join(xdg_config_home, "bpython") + return os.path.join(BaseDirectory.xdg_config_home, "bpython") def default_config_path(): diff --git a/requirements.txt b/requirements.txt index 5cb3eba12..bfbb71cdc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ curtsies >=0.3.3 greenlet requests setuptools +xdg diff --git a/setup.py b/setup.py index c49251f9b..e20a0be80 100755 --- a/setup.py +++ b/setup.py @@ -174,6 +174,7 @@ def git_describe_to_python_version(version): "curtsies >=0.3.3", "greenlet", "wcwidth", + "xdg", ] extras_require = { From 57a97d8fc3edf10276fc5ef4987234a45eaf2515 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 31 Dec 2020 13:38:27 +0100 Subject: [PATCH 1126/1650] Simplify path handling using pathlib --- bpython/config.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 08eeec920..9cafa3baf 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -1,8 +1,9 @@ import os import sys import locale -from itertools import chain from configparser import ConfigParser +from itertools import chain +from pathlib import Path from xdg import BaseDirectory from .autocomplete import AutocompleteModes @@ -35,12 +36,12 @@ def supports_box_chars(): def get_config_home(): """Returns the base directory for bpython's configuration files.""" - return os.path.join(BaseDirectory.xdg_config_home, "bpython") + return Path(BaseDirectory.xdg_config_home) / "bpython" def default_config_path(): """Returns bpython's default configuration file path.""" - return os.path.join(get_config_home(), "config") + return get_config_home() / "config" def fill_config_with_default_values(config, default_values): @@ -53,11 +54,9 @@ def fill_config_with_default_values(config, default_values): config.set(section, opt, f"{val}") -def loadini(struct, configfile): +def loadini(struct, config_path): """Loads .ini configuration file and stores its values in struct""" - config_path = os.path.expanduser(configfile) - config = ConfigParser() defaults = { "general": { @@ -287,18 +286,15 @@ def get_key_no_doublebind(command): else: struct.color_scheme = dict() - theme_filename = color_scheme_name + ".theme" - path = os.path.expanduser( - os.path.join(get_config_home(), theme_filename) - ) + path = get_config_home() / f"{color_scheme_name}.theme" try: load_theme(struct, path, struct.color_scheme, default_colors) except OSError: - sys.stderr.write(f"Could not load theme '{color_scheme_name}'.\n") + sys.stderr.write(f"Could not load theme '{color_scheme_name}' from {path}.\n") sys.exit(1) # expand path of history file - struct.hist_file = os.path.expanduser(struct.hist_file) + struct.hist_file = Path(struct.hist_file).expanduser() # verify completion mode if struct.autocomplete_mode is None: From 68c953871d5a8205de75abdef2ec4d41fcfa3b64 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 31 Dec 2020 13:43:27 +0100 Subject: [PATCH 1127/1650] Fix (py)xdg package name --- README.rst | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 059e78d77..0ff6279e5 100644 --- a/README.rst +++ b/README.rst @@ -157,8 +157,8 @@ Dependencies * Pygments * curtsies >= 0.3.3 * greenlet +* pyxdg * requests -* xdg * Sphinx >= 1.5 (optional, for the documentation) * babel (optional, for internationalization) * jedi (optional, for experimental multiline completion) diff --git a/requirements.txt b/requirements.txt index bfbb71cdc..4a9e91bca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Pygments curtsies >=0.3.3 greenlet +pyxdg requests setuptools -xdg diff --git a/setup.py b/setup.py index e20a0be80..64421fb31 100755 --- a/setup.py +++ b/setup.py @@ -174,7 +174,7 @@ def git_describe_to_python_version(version): "curtsies >=0.3.3", "greenlet", "wcwidth", - "xdg", + "pyxdg", ] extras_require = { From a3179a9754da2106831b2e17f6d4019183df3fc7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 31 Dec 2020 13:44:01 +0100 Subject: [PATCH 1128/1650] Add license header --- bpython/autocomplete.py | 1 - bpython/config.py | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 6b6698b13..94841e044 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -20,7 +20,6 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# import __main__ diff --git a/bpython/config.py b/bpython/config.py index 9cafa3baf..70708fd77 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -1,3 +1,27 @@ +# The MIT License +# +# Copyright (c) 2009-2015 the bpython authors. +# Copyright (c) 2015-2020 Sebastian Ramacher +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + + import os import sys import locale From 3a1c1b09ff4affa140ed2c4fcf32dda07d3f966e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 31 Dec 2020 13:51:50 +0100 Subject: [PATCH 1129/1650] Fix Python 3 compatibility --- bpython/simplerepl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/simplerepl.py b/bpython/simplerepl.py index 24f7380e8..9723ace5c 100644 --- a/bpython/simplerepl.py +++ b/bpython/simplerepl.py @@ -64,7 +64,7 @@ def request_undo(self, n=1): def out(self, msg): if hasattr(self, "orig_stdout"): - self.orig_stdout.write((msg + "\n").encode("utf8")) + self.orig_stdout.write(f"{msg}\n") self.orig_stdout.flush() else: print(msg) @@ -89,7 +89,7 @@ def print_padded(s): print_padded("") self.out("X``" + ("`" * (self.width + 2)) + "``X") for line in arr: - self.out("X```" + unicode(line.ljust(self.width)) + "```X") + self.out("X```" + line.ljust(self.width) + "```X") logger.debug("line:") logger.debug(repr(line)) self.out("X``" + ("`" * (self.width + 2)) + "``X") From 248f2e2966331277d81606b1fa2da8ade5af3f22 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 31 Dec 2020 14:05:01 +0100 Subject: [PATCH 1130/1650] Move simplerepl.py to the documentation (fixes #870) --- doc/sphinx/source/index.rst | 1 + {bpython => doc/sphinx/source}/simplerepl.py | 0 doc/sphinx/source/simplerepl.rst | 9 +++++++++ 3 files changed, 10 insertions(+) rename {bpython => doc/sphinx/source}/simplerepl.py (100%) create mode 100644 doc/sphinx/source/simplerepl.rst diff --git a/doc/sphinx/source/index.rst b/doc/sphinx/source/index.rst index 3322f2c6c..f209d2971 100644 --- a/doc/sphinx/source/index.rst +++ b/doc/sphinx/source/index.rst @@ -23,3 +23,4 @@ Contents: bpaste tips bpdb + simplerepl diff --git a/bpython/simplerepl.py b/doc/sphinx/source/simplerepl.py similarity index 100% rename from bpython/simplerepl.py rename to doc/sphinx/source/simplerepl.py diff --git a/doc/sphinx/source/simplerepl.rst b/doc/sphinx/source/simplerepl.rst new file mode 100644 index 000000000..8a088ad73 --- /dev/null +++ b/doc/sphinx/source/simplerepl.rst @@ -0,0 +1,9 @@ +.. _simplerepl: + +A Simple REPL +============= + +The following code listing shows a simple example REPL implemented using `bpython` and `curtsies`. + +.. literalinclude:: simplerepl.py + :language: python From 2d56c5c8035b7771d0801031d01065b4e84cc8cf Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 1 Jan 2021 12:56:09 +0100 Subject: [PATCH 1131/1650] Use more f-strings --- bpython/config.py | 4 +++- bpython/keys.py | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 70708fd77..2d96e0310 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -314,7 +314,9 @@ def get_key_no_doublebind(command): try: load_theme(struct, path, struct.color_scheme, default_colors) except OSError: - sys.stderr.write(f"Could not load theme '{color_scheme_name}' from {path}.\n") + sys.stderr.write( + f"Could not load theme '{color_scheme_name}' from {path}.\n" + ) sys.exit(1) # expand path of history file diff --git a/bpython/keys.py b/bpython/keys.py index 86984db6b..3da060d2c 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -37,8 +37,7 @@ def __getitem__(self, key): return self.map[key] else: raise KeyError( - "Configured keymap (%s)" % key - + " does not exist in bpython.keys" + f"Configured keymap ({key}) does not exist in bpython.keys" ) def __delitem__(self, key): From ea58d89c2f27da98f0d27cf919acb2b9a21fcd0f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 1 Jan 2021 13:12:55 +0100 Subject: [PATCH 1132/1650] Use pathlib instead of low-level os module --- bpython/config.py | 2 +- bpython/repl.py | 33 +++++++++++++++------------------ bpython/test/test_repl.py | 3 ++- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 2d96e0310..e1bc04419 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -203,7 +203,7 @@ def get_key_no_doublebind(command): return requested_key - struct.config_path = config_path + struct.config_path = Path(config_path).absolute() struct.dedent_after = config.getint("general", "dedent_after") struct.tab_length = config.getint("general", "tab_length") diff --git a/bpython/repl.py b/bpython/repl.py index 5fb75f04a..9939d7808 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -33,9 +33,10 @@ import textwrap import time import traceback +from enum import Enum from itertools import takewhile +from pathlib import Path from types import ModuleType -from enum import Enum from pygments.token import Token from pygments.lexers import Python3Lexer @@ -435,11 +436,10 @@ def __init__(self, interp, config): self.closed = False self.clipboard = get_clipboard() - pythonhist = os.path.expanduser(self.config.hist_file) - if os.path.exists(pythonhist): + if self.config.hist_file.exists(): try: self.rl_history.load( - pythonhist, getpreferredencoding() or "ascii" + self.config.hist_file, getpreferredencoding() or "ascii" ) except OSError: pass @@ -829,13 +829,13 @@ def write2file(self): self.interact.notify(_("Save cancelled.")) return - if fn.startswith("~"): - fn = os.path.expanduser(fn) - if not fn.endswith(".py") and self.config.save_append_py: - fn = fn + ".py" + fn = Path(fn).expanduser() + if fn.suffix != ".py" and self.config.save_append_py: + # fn.with_suffix(".py") does not append if fn has a non-empty suffix + fn = Path(f"{fn}.py") mode = "w" - if os.path.exists(fn): + if fn.exists(): mode = self.interact.file_prompt( _( "%s already exists. Do you " @@ -941,10 +941,9 @@ def push(self, s, insert_into_history=True): return more def insert_into_history(self, s): - pythonhist = os.path.expanduser(self.config.hist_file) try: self.rl_history.append_reload_and_write( - s, pythonhist, getpreferredencoding() + s, self.config.hist_file, getpreferredencoding() ) except RuntimeError as e: self.interact.notify(f"{e}") @@ -1147,7 +1146,7 @@ def open_in_external_editor(self, filename): return subprocess.call(args) == 0 def edit_config(self): - if not os.path.isfile(self.config.config_path): + if not self.config.config_path.is_file(): if self.interact.confirm( _( "Config file does not exist - create " @@ -1160,17 +1159,15 @@ def edit_config(self): ) # Py3 files need unicode default_config = default_config.decode("ascii") - containing_dir = os.path.dirname( - os.path.abspath(self.config.config_path) - ) - if not os.path.exists(containing_dir): - os.makedirs(containing_dir) + containing_dir = self.config.config_path.parent + if not containing_dir.exists(): + containing_dir.mkdir(parents=True) with open(self.config.config_path, "w") as f: f.write(default_config) except OSError as e: self.interact.notify( _("Error writing file '%s': %s") - % (self.config.config.path, e) + % (self.config.config_path, e) ) return False else: diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 1c56f8444..250dbf241 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -8,6 +8,7 @@ import unittest from itertools import islice +from pathlib import Path from unittest import mock from bpython import config, repl, cli, autocomplete @@ -332,7 +333,7 @@ def setUp(self): def test_create_config(self): tmp_dir = tempfile.mkdtemp() try: - config_path = os.path.join(tmp_dir, "newdir", "config") + config_path = Path(tmp_dir) / "newdir" / "config" self.repl.config.config_path = config_path self.repl.edit_config() self.assertTrue(os.path.exists(config_path)) From 2aed6b325fdd752d6a21e853e26d96278b3dc94d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 1 Jan 2021 13:50:08 +0100 Subject: [PATCH 1133/1650] Use temporary directory --- bpython/test/test_repl.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 250dbf241..4eb05c4b9 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,7 +1,6 @@ import collections import inspect import os -import shutil import socket import sys import tempfile @@ -331,15 +330,11 @@ def setUp(self): self.repl.config.editor = "true" def test_create_config(self): - tmp_dir = tempfile.mkdtemp() - try: + with tempfile.TemporaryDirectory() as tmp_dir: config_path = Path(tmp_dir) / "newdir" / "config" self.repl.config.config_path = config_path self.repl.edit_config() self.assertTrue(os.path.exists(config_path)) - finally: - shutil.rmtree(tmp_dir) - self.assertFalse(os.path.exists(config_path)) class TestRepl(unittest.TestCase): From b7a6753fc06abcad03e6331de712e364c30d52af Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 1 Jan 2021 14:05:27 +0100 Subject: [PATCH 1134/1650] Use pathlib instead of low-level os module --- bpython/test/__init__.py | 3 +- bpython/test/test_crashers.py | 2 +- bpython/test/test_importcompletion.py | 98 +++++++++++++-------------- bpython/test/test_repl.py | 3 +- 4 files changed, 50 insertions(+), 56 deletions(-) diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index cb9caef5f..7722278cc 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -1,6 +1,7 @@ import unittest import unittest.mock import os +from pathlib import Path from bpython.translations import init @@ -16,4 +17,4 @@ class MagicIterMock(unittest.mock.MagicMock): __next__ = unittest.mock.Mock(return_value=None) -TEST_CONFIG = os.path.join(os.path.dirname(__file__), "test.config") +TEST_CONFIG = Path(__file__).parent / "test.config" diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 3315f418e..4dd80e569 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -93,7 +93,7 @@ def processExited(self, reason): "-m", "bpython." + self.backend, "--config", - TEST_CONFIG, + str(TEST_CONFIG), ), env=dict(TERM="vt100", LANG=os.environ.get("LANG", "")), usePTY=(master, slave, os.ttyname(slave)), diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index b21207e11..ee9651a7b 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -56,43 +56,36 @@ def test_from_package(self): class TestAvoidSymbolicLinks(unittest.TestCase): def setUp(self): with tempfile.TemporaryDirectory() as import_test_folder: - os.mkdir(os.path.join(import_test_folder, "Level0")) - os.mkdir(os.path.join(import_test_folder, "Right")) - os.mkdir(os.path.join(import_test_folder, "Left")) - - current_path = os.path.join(import_test_folder, "Level0") - Path(os.path.join(current_path, "__init__.py")).touch() - - current_path = os.path.join(current_path, "Level1") - os.mkdir(current_path) - Path(os.path.join(current_path, "__init__.py")).touch() - - current_path = os.path.join(current_path, "Level2") - os.mkdir(current_path) - Path(os.path.join(current_path, "__init__.py")).touch() - - os.symlink( - os.path.join(import_test_folder, "Level0", "Level1"), - os.path.join(current_path, "Level3"), - True, + base_path = Path(import_test_folder) + (base_path / "Level0" / "Level1" / "Level2").mkdir(parents=True) + (base_path / "Left").mkdir(parents=True) + (base_path / "Right").mkdir(parents=True) + + current_path = base_path / "Level0" + (current_path / "__init__.py").touch() + + current_path = current_path / "Level1" + (current_path / "__init__.py").touch() + + current_path = current_path / "Level2" + (current_path / "__init__.py").touch() + # Level0/Level1/Level2/Level3 -> Level0/Level1 + (current_path / "Level3").symlink_to( + base_path / "Level0" / "Level1", target_is_directory=True ) - current_path = os.path.join(import_test_folder, "Right") - Path(os.path.join(current_path, "__init__.py")).touch() - - os.symlink( - os.path.join(import_test_folder, "Left"), - os.path.join(current_path, "toLeft"), - True, + current_path = base_path / "Right" + (current_path / "__init__.py").touch() + # Right/toLeft -> Left + (current_path / "toLeft").symlink_to( + base_path / "Left", target_is_directory=True ) - current_path = os.path.join(import_test_folder, "Left") - Path(os.path.join(current_path, "__init__.py")).touch() - - os.symlink( - os.path.join(import_test_folder, "Right"), - os.path.join(current_path, "toRight"), - True, + current_path = base_path / "Left" + (current_path / "__init__.py").touch() + # Left/toRight -> Right + (current_path / "toRight").symlink_to( + base_path / "Right", target_is_directory=True ) self.module_gatherer = ModuleGatherer( @@ -100,27 +93,28 @@ def setUp(self): ) while self.module_gatherer.find_coroutine(): pass - self.filepaths = [ - "Left.toRight.toLeft", - "Left.toRight", - "Left", - "Level0.Level1.Level2.Level3", - "Level0.Level1.Level2", - "Level0.Level1", - "Level0", - "Right", - "Right.toLeft", - "Right.toLeft.toRight", - ] def test_simple_symbolic_link_loop(self): + filepaths = [ + "Left.toRight.toLeft", + "Left.toRight", + "Left", + "Level0.Level1.Level2.Level3", + "Level0.Level1.Level2", + "Level0.Level1", + "Level0", + "Right", + "Right.toLeft", + "Right.toLeft.toRight", + ] + for thing in self.module_gatherer.modules: - self.assertTrue(thing in self.filepaths) + self.assertIn(thing, filepaths) if thing == "Left.toRight.toLeft": - self.filepaths.remove("Right.toLeft") - self.filepaths.remove("Right.toLeft.toRight") + filepaths.remove("Right.toLeft") + filepaths.remove("Right.toLeft.toRight") if thing == "Right.toLeft.toRight": - self.filepaths.remove("Left.toRight.toLeft") - self.filepaths.remove("Left.toRight") - self.filepaths.remove(thing) - self.assertFalse(self.filepaths) + filepaths.remove("Left.toRight.toLeft") + filepaths.remove("Left.toRight") + filepaths.remove(thing) + self.assertFalse(filepaths) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 4eb05c4b9..2d33430fe 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,6 +1,5 @@ import collections import inspect -import os import socket import sys import tempfile @@ -334,7 +333,7 @@ def test_create_config(self): config_path = Path(tmp_dir) / "newdir" / "config" self.repl.config.config_path = config_path self.repl.edit_config() - self.assertTrue(os.path.exists(config_path)) + self.assertTrue(config_path.exists()) class TestRepl(unittest.TestCase): From 163b64fe5a9743208e9ba77cd1a3fd6c492b8a9e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 1 Jan 2021 14:36:09 +0100 Subject: [PATCH 1135/1650] Refactor --- bpython/config.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index e1bc04419..4b7f33a45 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -68,6 +68,11 @@ def default_config_path(): return get_config_home() / "config" +def default_editor(): + """Returns the default editor.""" + return os.environ.get("VISUAL", os.environ.get("EDITOR", "vi")) + + def fill_config_with_default_values(config, default_values): for section in default_values.keys(): if not config.has_section(section): @@ -91,7 +96,7 @@ def loadini(struct, config_path): "complete_magic_methods": True, "dedent_after": 1, "default_autoreload": False, - "editor": os.environ.get("VISUAL", os.environ.get("EDITOR", "vi")), + "editor": default_editor(), "flush_output": True, "import_completion_skiplist": ":".join( ( From 39e75dc5174205fa8f9893fd989e8f496001c5cf Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 1 Jan 2021 14:45:06 +0100 Subject: [PATCH 1136/1650] Make config non-optional --- bpython/curtsies.py | 2 +- bpython/curtsiesfrontend/repl.py | 13 ++----------- bpython/test/test_curtsies_repl.py | 2 +- doc/sphinx/source/simplerepl.py | 17 ++++++++++------- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 0c0b44789..f9950997e 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -53,8 +53,8 @@ def __init__(self, config, locals_, banner, interp=None): pass # temp hack to get .original_stty super().__init__( + config, locals_=locals_, - config=config, banner=banner, interp=interp, orig_tcattrs=self.input_generator.original_stty, diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9af2c4bbc..9e47ebb18 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -27,12 +27,7 @@ import bpython from bpython.repl import Repl as BpythonRepl, SourceNotFound from bpython.repl import LineTypeTranslator as LineType -from bpython.config import ( - Struct, - loadini, - default_config_path, - getpreferredencoding, -) +from bpython.config import getpreferredencoding, default_config_path from bpython.formatter import BPythonFormatter from bpython import autocomplete from bpython.translations import _ @@ -310,8 +305,8 @@ class BaseRepl(BpythonRepl): def __init__( self, + config, locals_=None, - config=None, banner=None, interp=None, orig_tcattrs=None, @@ -326,10 +321,6 @@ def __init__( logger.debug("starting init") - if config is None: - config = Struct() - loadini(config, default_config_path()) - # If creating a new interpreter on undo would be unsafe because initial # state was passed in self.weak_rewind = bool(locals_ or interp) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 4a7544d1b..a95990279 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -233,7 +233,7 @@ def captured_output(): def create_repl(**kwargs): config = setup_config({"editor": "true"}) - repl = curtsiesrepl.BaseRepl(config=config, **kwargs) + repl = curtsiesrepl.BaseRepl(config, **kwargs) os.environ["PAGER"] = "true" os.environ.pop("PYTHONSTARTUP", None) repl.width = 50 diff --git a/doc/sphinx/source/simplerepl.py b/doc/sphinx/source/simplerepl.py index 9723ace5c..e0bddb480 100644 --- a/doc/sphinx/source/simplerepl.py +++ b/doc/sphinx/source/simplerepl.py @@ -27,10 +27,11 @@ import time import logging -from . import translations -from .curtsiesfrontend import events as bpythonevents -from .curtsiesfrontend.repl import BaseRepl -from .importcompletion import ModuleGatherer +from bpython import translations +from bpython.config import Struct, loadini, default_config_path +from bpython.curtsiesfrontend import events as bpythonevents +from bpython.curtsiesfrontend.repl import BaseRepl +from bpython.importcompletion import ModuleGatherer from curtsies.configfile_keynames import keymap as key_dispatch @@ -39,9 +40,9 @@ class SimpleRepl(BaseRepl): - def __init__(self): + def __init__(self, config): self.requested_events = [] - BaseRepl.__init__(self) + BaseRepl.__init__(self, config) def _request_refresh(self): self.requested_events.append(bpythonevents.RefreshRequestEvent()) @@ -116,10 +117,12 @@ def get_input(self): def main(args=None, locals_=None, banner=None): translations.init() + config = Struct() + loadini(config, default_config_path()) module_gatherer = ModuleGatherer() while module_gatherer.find_coroutine(): pass - with SimpleRepl() as r: + with SimpleRepl(config) as r: r.width = 50 r.height = 10 while True: From 7049f1c5c2964f6851420c7540a53b91faaa9751 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 1 Jan 2021 14:56:24 +0100 Subject: [PATCH 1137/1650] Display actual config file in help message --- bpython/curtsiesfrontend/repl.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9e47ebb18..8a7a7ff04 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -27,7 +27,7 @@ import bpython from bpython.repl import Repl as BpythonRepl, SourceNotFound from bpython.repl import LineTypeTranslator as LineType -from bpython.config import getpreferredencoding, default_config_path +from bpython.config import getpreferredencoding from bpython.formatter import BPythonFormatter from bpython import autocomplete from bpython.translations import _ @@ -61,8 +61,7 @@ HELP_MESSAGE = """ Thanks for using bpython! -See http://bpython-interpreter.org/ for more information and -http://docs.bpython-interpreter.org/ for docs. +See http://bpython-interpreter.org/ for more information and http://docs.bpython-interpreter.org/ for docs. Please report issues at https://github.com/bpython/bpython/issues Features: @@ -75,7 +74,7 @@ bpython -i your_script.py runs a file in interactive mode bpython -t your_script.py pastes the contents of a file into the session -A config file at {config_file_location} customizes keys and behavior of bpython. +A config file at {config.config_path} customizes keys and behavior of bpython. You can also set which pastebin helper and which external editor to use. See {example_config_url} for an example config file. Press {config.edit_config_key} to edit this config file. @@ -1983,7 +1982,6 @@ def version_help_text(self): + ("using curtsies version %s" % curtsies.__version__) + "\n" + HELP_MESSAGE.format( - config_file_location=default_config_path(), example_config_url=EXAMPLE_CONFIG_URL, config=self.config, ) From d2600fca8a04ad80d6776e6d7a67672fcbb4f022 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 1 Jan 2021 15:21:34 +0100 Subject: [PATCH 1138/1650] Make help message translatable --- bpython/curtsiesfrontend/repl.py | 54 +++--- bpython/translations/bpython.pot | 154 +++++++++++------- .../translations/de/LC_MESSAGES/bpython.po | 148 ++++++++++------- .../translations/es_ES/LC_MESSAGES/bpython.po | 148 ++++++++++------- .../translations/fr_FR/LC_MESSAGES/bpython.po | 148 ++++++++++------- .../translations/it_IT/LC_MESSAGES/bpython.po | 148 ++++++++++------- .../translations/nl_NL/LC_MESSAGES/bpython.po | 148 ++++++++++------- 7 files changed, 566 insertions(+), 382 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 8a7a7ff04..2d58877f4 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -58,27 +58,6 @@ INCONSISTENT_HISTORY_MSG = "#<---History inconsistent with output shown--->" CONTIGUITY_BROKEN_MSG = "#<---History contiguity broken by rewind--->" -HELP_MESSAGE = """ -Thanks for using bpython! - -See http://bpython-interpreter.org/ for more information and http://docs.bpython-interpreter.org/ for docs. -Please report issues at https://github.com/bpython/bpython/issues - -Features: -Try using undo ({config.undo_key})! -Edit the current line ({config.edit_current_block_key}) or the entire session ({config.external_editor_key}) in an external editor. (currently {config.editor}) -Save sessions ({config.save_key}) or post them to pastebins ({config.pastebin_key})! Current pastebin helper: {config.pastebin_helper} -Reload all modules and rerun session ({config.reimport_key}) to test out changes to a module. -Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute the current session when a module you've imported is modified. - -bpython -i your_script.py runs a file in interactive mode -bpython -t your_script.py pastes the contents of a file into the session - -A config file at {config.config_path} customizes keys and behavior of bpython. -You can also set which pastebin helper and which external editor to use. -See {example_config_url} for an example config file. -Press {config.edit_config_key} to edit this config file. -""" EXAMPLE_CONFIG_URL = "https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config" EDIT_SESSION_HEADER = """### current bpython session - make changes and save to reevaluate session. ### lines beginning with ### will be ignored. @@ -1976,16 +1955,29 @@ def help_text(self): return self.version_help_text() + "\n" + self.key_help_text() def version_help_text(self): - return ( - ("bpython-curtsies version %s" % bpython.__version__) - + " " - + ("using curtsies version %s" % curtsies.__version__) - + "\n" - + HELP_MESSAGE.format( - example_config_url=EXAMPLE_CONFIG_URL, - config=self.config, - ) - ) + help_message = _(""" +Thanks for using bpython! + +See http://bpython-interpreter.org/ for more information and http://docs.bpython-interpreter.org/ for docs. +Please report issues at https://github.com/bpython/bpython/issues + +Features: +Try using undo ({config.undo_key})! +Edit the current line ({config.edit_current_block_key}) or the entire session ({config.external_editor_key}) in an external editor. (currently {config.editor}) +Save sessions ({config.save_key}) or post them to pastebins ({config.pastebin_key})! Current pastebin helper: {config.pastebin_helper} +Reload all modules and rerun session ({config.reimport_key}) to test out changes to a module. +Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute the current session when a module you've imported is modified. + +bpython -i your_script.py runs a file in interactive mode +bpython -t your_script.py pastes the contents of a file into the session + +A config file at {config.config_path} customizes keys and behavior of bpython. +You can also set which pastebin helper and which external editor to use. +See {example_config_url} for an example config file. +Press {config.edit_config_key} to edit this config file. +""").format(example_config_url=EXAMPLE_CONFIG_URL, config=self.config) + + return f"bpython-curtsies version {bpython.__version__} using curtsies version {curtsies.__version__}\n{help_message}" def key_help_text(self): NOT_IMPLEMENTED = ( diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index e8dc384fa..df60dee43 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -1,14 +1,14 @@ # Translations template for bpython. -# Copyright (C) 2020 ORGANIZATION +# Copyright (C) 2021 ORGANIZATION # This file is distributed under the same license as the bpython project. -# FIRST AUTHOR , 2020. +# FIRST AUTHOR , 2021. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.21.dev78\n" +"Project-Id-Version: bpython 0.20.1.post128\n" "Report-Msgid-Bugs-To: https://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-10-29 12:28+0100\n" +"POT-Creation-Date: 2021-01-01 15:21+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -47,54 +47,54 @@ msgid "" "script." msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "y" msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "yes" msgstr "" -#: bpython/cli.py:1695 +#: bpython/cli.py:1692 msgid "Rewind" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1693 msgid "Save" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1694 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1695 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1696 msgid "Show Source" msgstr "" -#: bpython/cli.py:1946 +#: bpython/cli.py:1943 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsies.py:137 +#: bpython/curtsies.py:136 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:148 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:150 +#: bpython/curtsies.py:149 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" @@ -124,208 +124,240 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:653 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:663 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:665 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:820 +#: bpython/repl.py:824 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:840 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:844 +#: bpython/repl.py:848 msgid "overwrite" msgstr "" -#: bpython/repl.py:846 +#: bpython/repl.py:850 msgid "append" msgstr "" -#: bpython/repl.py:858 bpython/repl.py:1168 +#: bpython/repl.py:862 bpython/repl.py:1169 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:860 +#: bpython/repl.py:864 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:866 +#: bpython/repl.py:870 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:873 +#: bpython/repl.py:877 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:879 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:884 +#: bpython/repl.py:888 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:890 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:898 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:904 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:908 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:913 +#: bpython/repl.py:917 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:918 +#: bpython/repl.py:922 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:957 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:965 bpython/repl.py:969 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:972 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1148 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1178 +#: bpython/repl.py:1179 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1184 +#: bpython/repl.py:1185 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:605 +#: bpython/urwid.py:604 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -#: bpython/urwid.py:1115 +#: bpython/urwid.py:1114 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1120 +#: bpython/urwid.py:1119 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1127 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1132 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1142 +#: bpython/urwid.py:1141 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1336 +#: bpython/urwid.py:1335 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:343 +#: bpython/curtsiesfrontend/repl.py:312 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:314 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:674 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:692 +#: bpython/curtsiesfrontend/repl.py:661 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1001 +#: bpython/curtsiesfrontend/repl.py:970 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:985 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:995 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1006 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1012 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1048 +#: bpython/curtsiesfrontend/repl.py:1017 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1054 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Auto-reloading not available because watchdog not installed." msgstr "" +#: bpython/curtsiesfrontend/repl.py:1958 +msgid "" +"\n" +"Thanks for using bpython!\n" +"\n" +"See http://bpython-interpreter.org/ for more information and http://docs" +".bpython-interpreter.org/ for docs.\n" +"Please report issues at https://github.com/bpython/bpython/issues\n" +"\n" +"Features:\n" +"Try using undo ({config.undo_key})!\n" +"Edit the current line ({config.edit_current_block_key}) or the entire " +"session ({config.external_editor_key}) in an external editor. (currently " +"{config.editor})\n" +"Save sessions ({config.save_key}) or post them to pastebins " +"({config.pastebin_key})! Current pastebin helper: " +"{config.pastebin_helper}\n" +"Reload all modules and rerun session ({config.reimport_key}) to test out " +"changes to a module.\n" +"Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute " +"the current session when a module you've imported is modified.\n" +"\n" +"bpython -i your_script.py runs a file in interactive mode\n" +"bpython -t your_script.py pastes the contents of a file into the session\n" +"\n" +"A config file at {config.config_path} customizes keys and behavior of " +"bpython.\n" +"You can also set which pastebin helper and which external editor to use.\n" +"See {example_config_url} for an example config file.\n" +"Press {config.edit_config_key} to edit this config file.\n" +msgstr "" + diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 951c1cc0b..e903a3ee7 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-10-29 12:28+0100\n" +"POT-Creation-Date: 2021-01-01 15:21+0100\n" "PO-Revision-Date: 2020-10-29 12:26+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: de\n" @@ -51,35 +51,35 @@ msgid "" "script." msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "y" msgstr "j" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "yes" msgstr "ja" -#: bpython/cli.py:1695 +#: bpython/cli.py:1692 msgid "Rewind" msgstr "Rückgängig" -#: bpython/cli.py:1696 +#: bpython/cli.py:1693 msgid "Save" msgstr "Speichern" -#: bpython/cli.py:1697 +#: bpython/cli.py:1694 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1695 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1696 msgid "Show Source" msgstr "Quellcode anzeigen" -#: bpython/cli.py:1946 +#: bpython/cli.py:1943 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" @@ -89,19 +89,19 @@ msgstr "" "`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " "unterstützt und wird in einer zukünftigen Version entfernt werden." -#: bpython/curtsies.py:137 +#: bpython/curtsies.py:136 msgid "log debug messages to bpython.log" msgstr "Zeichne debug Nachrichten in bpython.log auf" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:148 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:150 +#: bpython/curtsies.py:149 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "Zusätzliche Argumente spezifisch für die curtsies-basierte REPL." @@ -131,155 +131,155 @@ msgstr "Keine Ausgabe von Hilfsprogramm vorhanden." msgid "Failed to recognize the helper program's output as an URL." msgstr "Konnte Ausgabe von Hilfsprogramm nicht verarbeiten." -#: bpython/repl.py:653 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "Nichts um Quellcode abzurufen" -#: bpython/repl.py:658 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" -#: bpython/repl.py:663 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/repl.py:665 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" -#: bpython/repl.py:820 +#: bpython/repl.py:824 msgid "Save to file (Esc to cancel): " msgstr "In Datei speichern (Esc um abzubrechen): " -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 msgid "Save cancelled." msgstr "Speichern abgebrochen." -#: bpython/repl.py:836 +#: bpython/repl.py:840 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen? " -#: bpython/repl.py:844 +#: bpython/repl.py:848 msgid "overwrite" msgstr "überschreiben" -#: bpython/repl.py:846 +#: bpython/repl.py:850 msgid "append" msgstr "anhängen" -#: bpython/repl.py:858 bpython/repl.py:1168 +#: bpython/repl.py:862 bpython/repl.py:1169 #, python-format msgid "Error writing file '%s': %s" msgstr "Fehler beim Schreiben in Datei '%s': %s" -#: bpython/repl.py:860 +#: bpython/repl.py:864 #, python-format msgid "Saved to %s." msgstr "Nach %s gespeichert." -#: bpython/repl.py:866 +#: bpython/repl.py:870 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/repl.py:873 +#: bpython/repl.py:877 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/repl.py:875 +#: bpython/repl.py:879 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:884 +#: bpython/repl.py:888 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:890 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:898 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:904 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." -#: bpython/repl.py:904 +#: bpython/repl.py:908 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:913 +#: bpython/repl.py:917 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:918 +#: bpython/repl.py:922 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:957 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" "Wie viele Zeilen rückgängig machen? (Rückgängigmachen wird bis zu ~%.1f " "Sekunden brauchen) [1]" -#: bpython/repl.py:965 bpython/repl.py:969 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "Rückgängigmachen abgebrochen" -#: bpython/repl.py:972 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "Mache %d Zeile rückgängig... (ungefähr %.1f Sekunden)" msgstr[1] "Mache %d Zeilen rückgängig... (ungefähr %.1f Sekunden)" -#: bpython/repl.py:1148 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" "Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " "werden? (j/N)" -#: bpython/repl.py:1178 +#: bpython/repl.py:1179 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" "bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " "Änderungen übernommen werden." -#: bpython/repl.py:1184 +#: bpython/repl.py:1185 #, python-format msgid "Error editing config file: %s" msgstr "Fehler beim Bearbeiten der Konfigurationsdatei: %s" -#: bpython/urwid.py:605 +#: bpython/urwid.py:604 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rückgängigmachen <%s> Speichern <%s> Pastebin <%s> Pager <%s> " "Quellcode anzeigen " -#: bpython/urwid.py:1115 +#: bpython/urwid.py:1114 msgid "Run twisted reactor." msgstr "Führe twisted reactor aus." -#: bpython/urwid.py:1120 +#: bpython/urwid.py:1119 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "Wähle reactor aus (siehe --help-reactors). Impliziert --twisted." -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1127 msgid "List available reactors for -r." msgstr "Liste verfügbare reactors für -r auf." -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1132 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." @@ -287,11 +287,11 @@ msgstr "" "Auszuführendes twistd Plugin (starte twistd für eine Liste). Verwende " "\"--\" um Optionen an das Plugin zu übergeben." -#: bpython/urwid.py:1142 +#: bpython/urwid.py:1141 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1336 +#: bpython/urwid.py:1335 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" @@ -301,53 +301,85 @@ msgstr "" "`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " "unterstützt und wird in einer zukünftigen Version entfernt werden." -#: bpython/curtsiesfrontend/repl.py:343 +#: bpython/curtsiesfrontend/repl.py:312 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:314 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#: bpython/curtsiesfrontend/repl.py:674 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" -#: bpython/curtsiesfrontend/repl.py:692 +#: bpython/curtsiesfrontend/repl.py:661 #, python-format msgid "Reloaded at %s because %s modified." msgstr "Bei %s neugeladen, da %s modifiziert wurde." -#: bpython/curtsiesfrontend/repl.py:1001 +#: bpython/curtsiesfrontend/repl.py:970 msgid "Session not reevaluated because it was not edited" msgstr "Die Sitzung wurde nicht neu ausgeführt, da sie nicht berabeitet wurde" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:985 msgid "Session not reevaluated because saved file was blank" msgstr "Die Sitzung wurde nicht neu ausgeführt, da die gespeicherte Datei leer war" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:995 msgid "Session edited and reevaluated" msgstr "Sitzung bearbeitet und neu ausgeführt" -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1006 #, python-format msgid "Reloaded at %s by user." msgstr "Bei %s vom Benutzer neu geladen." -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1012 msgid "Auto-reloading deactivated." msgstr "Automatisches Neuladen deaktiviert." -#: bpython/curtsiesfrontend/repl.py:1048 +#: bpython/curtsiesfrontend/repl.py:1017 msgid "Auto-reloading active, watching for file changes..." msgstr "Automatisches Neuladen ist aktiv; beobachte Dateiänderungen..." -#: bpython/curtsiesfrontend/repl.py:1054 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Auto-reloading not available because watchdog not installed." msgstr "" "Automatisches Neuladen ist nicht verfügbar da watchdog nicht installiert " "ist." +#: bpython/curtsiesfrontend/repl.py:1958 +msgid "" +"\n" +"Thanks for using bpython!\n" +"\n" +"See http://bpython-interpreter.org/ for more information and http://docs" +".bpython-interpreter.org/ for docs.\n" +"Please report issues at https://github.com/bpython/bpython/issues\n" +"\n" +"Features:\n" +"Try using undo ({config.undo_key})!\n" +"Edit the current line ({config.edit_current_block_key}) or the entire " +"session ({config.external_editor_key}) in an external editor. (currently " +"{config.editor})\n" +"Save sessions ({config.save_key}) or post them to pastebins " +"({config.pastebin_key})! Current pastebin helper: " +"{config.pastebin_helper}\n" +"Reload all modules and rerun session ({config.reimport_key}) to test out " +"changes to a module.\n" +"Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute " +"the current session when a module you've imported is modified.\n" +"\n" +"bpython -i your_script.py runs a file in interactive mode\n" +"bpython -t your_script.py pastes the contents of a file into the session\n" +"\n" +"A config file at {config.config_path} customizes keys and behavior of " +"bpython.\n" +"You can also set which pastebin helper and which external editor to use.\n" +"See {example_config_url} for an example config file.\n" +"Press {config.edit_config_key} to edit this config file.\n" +msgstr "" + diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 7bb910396..526fb57ce 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-10-29 12:28+0100\n" +"POT-Creation-Date: 2021-01-01 15:21+0100\n" "PO-Revision-Date: 2020-10-29 12:22+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: es_ES\n" @@ -48,54 +48,54 @@ msgid "" "script." msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "y" msgstr "s" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "yes" msgstr "si" -#: bpython/cli.py:1695 +#: bpython/cli.py:1692 msgid "Rewind" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1693 msgid "Save" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1694 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1695 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1696 msgid "Show Source" msgstr "" -#: bpython/cli.py:1946 +#: bpython/cli.py:1943 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsies.py:137 +#: bpython/curtsies.py:136 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:148 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:150 +#: bpython/curtsies.py:149 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" @@ -125,210 +125,242 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:653 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:663 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:665 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:820 +#: bpython/repl.py:824 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:840 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:844 +#: bpython/repl.py:848 msgid "overwrite" msgstr "" -#: bpython/repl.py:846 +#: bpython/repl.py:850 msgid "append" msgstr "" -#: bpython/repl.py:858 bpython/repl.py:1168 +#: bpython/repl.py:862 bpython/repl.py:1169 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:860 +#: bpython/repl.py:864 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:866 +#: bpython/repl.py:870 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:873 +#: bpython/repl.py:877 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:879 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:884 +#: bpython/repl.py:888 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:890 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:898 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:904 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:908 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:913 +#: bpython/repl.py:917 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:918 +#: bpython/repl.py:922 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:957 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:965 bpython/repl.py:969 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:972 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1148 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1178 +#: bpython/repl.py:1179 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1184 +#: bpython/repl.py:1185 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:605 +#: bpython/urwid.py:604 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra el " "código fuente" -#: bpython/urwid.py:1115 +#: bpython/urwid.py:1114 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1120 +#: bpython/urwid.py:1119 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1127 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1132 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1142 +#: bpython/urwid.py:1141 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1336 +#: bpython/urwid.py:1335 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:343 +#: bpython/curtsiesfrontend/repl.py:312 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:314 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:674 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:692 +#: bpython/curtsiesfrontend/repl.py:661 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1001 +#: bpython/curtsiesfrontend/repl.py:970 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:985 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:995 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1006 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1012 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1048 +#: bpython/curtsiesfrontend/repl.py:1017 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1054 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Auto-reloading not available because watchdog not installed." msgstr "" +#: bpython/curtsiesfrontend/repl.py:1958 +msgid "" +"\n" +"Thanks for using bpython!\n" +"\n" +"See http://bpython-interpreter.org/ for more information and http://docs" +".bpython-interpreter.org/ for docs.\n" +"Please report issues at https://github.com/bpython/bpython/issues\n" +"\n" +"Features:\n" +"Try using undo ({config.undo_key})!\n" +"Edit the current line ({config.edit_current_block_key}) or the entire " +"session ({config.external_editor_key}) in an external editor. (currently " +"{config.editor})\n" +"Save sessions ({config.save_key}) or post them to pastebins " +"({config.pastebin_key})! Current pastebin helper: " +"{config.pastebin_helper}\n" +"Reload all modules and rerun session ({config.reimport_key}) to test out " +"changes to a module.\n" +"Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute " +"the current session when a module you've imported is modified.\n" +"\n" +"bpython -i your_script.py runs a file in interactive mode\n" +"bpython -t your_script.py pastes the contents of a file into the session\n" +"\n" +"A config file at {config.config_path} customizes keys and behavior of " +"bpython.\n" +"You can also set which pastebin helper and which external editor to use.\n" +"See {example_config_url} for an example config file.\n" +"Press {config.edit_config_key} to edit this config file.\n" +msgstr "" + diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index d3d00f08a..b62785548 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-10-29 12:28+0100\n" +"POT-Creation-Date: 2021-01-01 15:21+0100\n" "PO-Revision-Date: 2020-10-29 12:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: fr_FR\n" @@ -52,54 +52,54 @@ msgid "" "script." msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "y" msgstr "o" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "yes" msgstr "oui" -#: bpython/cli.py:1695 +#: bpython/cli.py:1692 msgid "Rewind" msgstr "Rembobiner" -#: bpython/cli.py:1696 +#: bpython/cli.py:1693 msgid "Save" msgstr "Sauvegarder" -#: bpython/cli.py:1697 +#: bpython/cli.py:1694 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1695 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1696 msgid "Show Source" msgstr "Montrer le code source" -#: bpython/cli.py:1946 +#: bpython/cli.py:1943 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsies.py:137 +#: bpython/curtsies.py:136 msgid "log debug messages to bpython.log" msgstr "logger les messages de debug dans bpython.log" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:148 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:150 +#: bpython/curtsies.py:149 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" @@ -129,149 +129,149 @@ msgstr "pas de sortie du programme externe." msgid "Failed to recognize the helper program's output as an URL." msgstr "la sortie du programme externe ne correspond pas à une URL." -#: bpython/repl.py:653 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "Impossible de récupérer le source: %s" -#: bpython/repl.py:663 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "Impossible d'accéder au source de %r" -#: bpython/repl.py:665 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "Pas de code source trouvé pour %s" -#: bpython/repl.py:820 +#: bpython/repl.py:824 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:840 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:844 +#: bpython/repl.py:848 msgid "overwrite" msgstr "" -#: bpython/repl.py:846 +#: bpython/repl.py:850 msgid "append" msgstr "" -#: bpython/repl.py:858 bpython/repl.py:1168 +#: bpython/repl.py:862 bpython/repl.py:1169 #, python-format msgid "Error writing file '%s': %s" msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" -#: bpython/repl.py:860 +#: bpython/repl.py:864 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:866 +#: bpython/repl.py:870 msgid "No clipboard available." msgstr "Pas de presse-papier disponible." -#: bpython/repl.py:873 +#: bpython/repl.py:877 msgid "Could not copy to clipboard." msgstr "Impossible de copier vers le presse-papier." -#: bpython/repl.py:875 +#: bpython/repl.py:879 msgid "Copied content to clipboard." msgstr "Contenu copié vers le presse-papier." -#: bpython/repl.py:884 +#: bpython/repl.py:888 msgid "Pastebin buffer? (y/N) " msgstr "Tampon Pastebin ? (o/N) " -#: bpython/repl.py:886 +#: bpython/repl.py:890 msgid "Pastebin aborted." msgstr "Pastebin abandonné." -#: bpython/repl.py:894 +#: bpython/repl.py:898 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" -#: bpython/repl.py:900 +#: bpython/repl.py:904 msgid "Posting data to pastebin..." msgstr "Envoi des donnés à pastebin..." -#: bpython/repl.py:904 +#: bpython/repl.py:908 #, python-format msgid "Upload failed: %s" msgstr "Echec du téléchargement: %s" -#: bpython/repl.py:913 +#: bpython/repl.py:917 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "URL Pastebin: %s - URL de suppression: %s" -#: bpython/repl.py:918 +#: bpython/repl.py:922 #, python-format msgid "Pastebin URL: %s" msgstr "URL Pastebin: %s" -#: bpython/repl.py:957 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:965 bpython/repl.py:969 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:972 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1148 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" -#: bpython/repl.py:1178 +#: bpython/repl.py:1179 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1184 +#: bpython/repl.py:1185 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:605 +#: bpython/urwid.py:604 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> " "Montrer Source " -#: bpython/urwid.py:1115 +#: bpython/urwid.py:1114 msgid "Run twisted reactor." msgstr "Lancer le reactor twisted." -#: bpython/urwid.py:1120 +#: bpython/urwid.py:1119 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1127 msgid "List available reactors for -r." msgstr "Lister les reactors disponibles pour -r." -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1132 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." @@ -279,62 +279,94 @@ msgstr "" "plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" " "pour donner plus d'options au plugin." -#: bpython/urwid.py:1142 +#: bpython/urwid.py:1141 msgid "Port to run an eval server on (forces Twisted)." msgstr "Port pour lancer un server eval (force Twisted)." -#: bpython/urwid.py:1336 +#: bpython/urwid.py:1335 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:343 +#: bpython/curtsiesfrontend/repl.py:312 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:314 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." -#: bpython/curtsiesfrontend/repl.py:674 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" -#: bpython/curtsiesfrontend/repl.py:692 +#: bpython/curtsiesfrontend/repl.py:661 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1001 +#: bpython/curtsiesfrontend/repl.py:970 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:985 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:995 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1006 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1012 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1048 +#: bpython/curtsiesfrontend/repl.py:1017 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1054 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Auto-reloading not available because watchdog not installed." msgstr "" +#: bpython/curtsiesfrontend/repl.py:1958 +msgid "" +"\n" +"Thanks for using bpython!\n" +"\n" +"See http://bpython-interpreter.org/ for more information and http://docs" +".bpython-interpreter.org/ for docs.\n" +"Please report issues at https://github.com/bpython/bpython/issues\n" +"\n" +"Features:\n" +"Try using undo ({config.undo_key})!\n" +"Edit the current line ({config.edit_current_block_key}) or the entire " +"session ({config.external_editor_key}) in an external editor. (currently " +"{config.editor})\n" +"Save sessions ({config.save_key}) or post them to pastebins " +"({config.pastebin_key})! Current pastebin helper: " +"{config.pastebin_helper}\n" +"Reload all modules and rerun session ({config.reimport_key}) to test out " +"changes to a module.\n" +"Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute " +"the current session when a module you've imported is modified.\n" +"\n" +"bpython -i your_script.py runs a file in interactive mode\n" +"bpython -t your_script.py pastes the contents of a file into the session\n" +"\n" +"A config file at {config.config_path} customizes keys and behavior of " +"bpython.\n" +"You can also set which pastebin helper and which external editor to use.\n" +"See {example_config_url} for an example config file.\n" +"Press {config.edit_config_key} to edit this config file.\n" +msgstr "" + diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index 7d0a39783..46ae2a155 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-10-29 12:28+0100\n" +"POT-Creation-Date: 2021-01-01 15:21+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: it_IT\n" @@ -48,54 +48,54 @@ msgid "" "script." msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "y" msgstr "s" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "yes" msgstr "si" -#: bpython/cli.py:1695 +#: bpython/cli.py:1692 msgid "Rewind" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1693 msgid "Save" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1694 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1695 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1696 msgid "Show Source" msgstr "" -#: bpython/cli.py:1946 +#: bpython/cli.py:1943 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsies.py:137 +#: bpython/curtsies.py:136 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:148 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:150 +#: bpython/curtsies.py:149 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" @@ -125,211 +125,243 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:653 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:663 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:665 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:820 +#: bpython/repl.py:824 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:840 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:844 +#: bpython/repl.py:848 msgid "overwrite" msgstr "" -#: bpython/repl.py:846 +#: bpython/repl.py:850 msgid "append" msgstr "" -#: bpython/repl.py:858 bpython/repl.py:1168 +#: bpython/repl.py:862 bpython/repl.py:1169 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:860 +#: bpython/repl.py:864 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:866 +#: bpython/repl.py:870 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:873 +#: bpython/repl.py:877 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:879 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:884 +#: bpython/repl.py:888 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:890 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:898 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:904 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:908 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:913 +#: bpython/repl.py:917 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:918 +#: bpython/repl.py:922 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:957 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:965 bpython/repl.py:969 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:972 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1148 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1178 +#: bpython/repl.py:1179 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1184 +#: bpython/repl.py:1185 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:605 +#: bpython/urwid.py:604 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" -#: bpython/urwid.py:1115 +#: bpython/urwid.py:1114 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1120 +#: bpython/urwid.py:1119 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1127 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1132 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1142 +#: bpython/urwid.py:1141 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1336 +#: bpython/urwid.py:1335 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:343 +#: bpython/curtsiesfrontend/repl.py:312 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:314 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:674 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:692 +#: bpython/curtsiesfrontend/repl.py:661 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1001 +#: bpython/curtsiesfrontend/repl.py:970 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:985 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:995 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1006 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1012 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1048 +#: bpython/curtsiesfrontend/repl.py:1017 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1054 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Auto-reloading not available because watchdog not installed." msgstr "" +#: bpython/curtsiesfrontend/repl.py:1958 +msgid "" +"\n" +"Thanks for using bpython!\n" +"\n" +"See http://bpython-interpreter.org/ for more information and http://docs" +".bpython-interpreter.org/ for docs.\n" +"Please report issues at https://github.com/bpython/bpython/issues\n" +"\n" +"Features:\n" +"Try using undo ({config.undo_key})!\n" +"Edit the current line ({config.edit_current_block_key}) or the entire " +"session ({config.external_editor_key}) in an external editor. (currently " +"{config.editor})\n" +"Save sessions ({config.save_key}) or post them to pastebins " +"({config.pastebin_key})! Current pastebin helper: " +"{config.pastebin_helper}\n" +"Reload all modules and rerun session ({config.reimport_key}) to test out " +"changes to a module.\n" +"Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute " +"the current session when a module you've imported is modified.\n" +"\n" +"bpython -i your_script.py runs a file in interactive mode\n" +"bpython -t your_script.py pastes the contents of a file into the session\n" +"\n" +"A config file at {config.config_path} customizes keys and behavior of " +"bpython.\n" +"You can also set which pastebin helper and which external editor to use.\n" +"See {example_config_url} for an example config file.\n" +"Press {config.edit_config_key} to edit this config file.\n" +msgstr "" + #~ msgid "Error editing config file." #~ msgstr "" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index 44f047281..be858262f 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2020-10-29 12:28+0100\n" +"POT-Creation-Date: 2021-01-01 15:21+0100\n" "PO-Revision-Date: 2020-10-29 12:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: nl_NL\n" @@ -48,54 +48,54 @@ msgid "" "script." msgstr "" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "y" msgstr "j" -#: bpython/cli.py:315 bpython/urwid.py:538 +#: bpython/cli.py:312 bpython/urwid.py:537 msgid "yes" msgstr "ja" -#: bpython/cli.py:1695 +#: bpython/cli.py:1692 msgid "Rewind" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1693 msgid "Save" msgstr "" -#: bpython/cli.py:1697 +#: bpython/cli.py:1694 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1698 +#: bpython/cli.py:1695 msgid "Pager" msgstr "" -#: bpython/cli.py:1699 +#: bpython/cli.py:1696 msgid "Show Source" msgstr "" -#: bpython/cli.py:1946 +#: bpython/cli.py:1943 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsies.py:137 +#: bpython/curtsies.py:136 msgid "log debug messages to bpython.log" msgstr "" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:148 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:150 +#: bpython/curtsies.py:149 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" @@ -125,208 +125,240 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:653 +#: bpython/repl.py:657 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:662 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:663 +#: bpython/repl.py:667 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:665 +#: bpython/repl.py:669 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:820 +#: bpython/repl.py:824 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:840 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:844 +#: bpython/repl.py:848 msgid "overwrite" msgstr "" -#: bpython/repl.py:846 +#: bpython/repl.py:850 msgid "append" msgstr "" -#: bpython/repl.py:858 bpython/repl.py:1168 +#: bpython/repl.py:862 bpython/repl.py:1169 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:860 +#: bpython/repl.py:864 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:866 +#: bpython/repl.py:870 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:873 +#: bpython/repl.py:877 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:879 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:884 +#: bpython/repl.py:888 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:890 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:898 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:904 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:908 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:913 +#: bpython/repl.py:917 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:918 +#: bpython/repl.py:922 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:957 +#: bpython/repl.py:960 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:965 bpython/repl.py:969 +#: bpython/repl.py:968 bpython/repl.py:972 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:972 +#: bpython/repl.py:975 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1148 +#: bpython/repl.py:1151 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1178 +#: bpython/repl.py:1179 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1184 +#: bpython/repl.py:1185 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:605 +#: bpython/urwid.py:604 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" -#: bpython/urwid.py:1115 +#: bpython/urwid.py:1114 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1120 +#: bpython/urwid.py:1119 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1128 +#: bpython/urwid.py:1127 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1133 +#: bpython/urwid.py:1132 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1142 +#: bpython/urwid.py:1141 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1336 +#: bpython/urwid.py:1335 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:343 +#: bpython/curtsiesfrontend/repl.py:312 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:345 +#: bpython/curtsiesfrontend/repl.py:314 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:674 +#: bpython/curtsiesfrontend/repl.py:643 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:692 +#: bpython/curtsiesfrontend/repl.py:661 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1001 +#: bpython/curtsiesfrontend/repl.py:970 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:985 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:995 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1006 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1012 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1048 +#: bpython/curtsiesfrontend/repl.py:1017 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1054 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Auto-reloading not available because watchdog not installed." msgstr "" +#: bpython/curtsiesfrontend/repl.py:1958 +msgid "" +"\n" +"Thanks for using bpython!\n" +"\n" +"See http://bpython-interpreter.org/ for more information and http://docs" +".bpython-interpreter.org/ for docs.\n" +"Please report issues at https://github.com/bpython/bpython/issues\n" +"\n" +"Features:\n" +"Try using undo ({config.undo_key})!\n" +"Edit the current line ({config.edit_current_block_key}) or the entire " +"session ({config.external_editor_key}) in an external editor. (currently " +"{config.editor})\n" +"Save sessions ({config.save_key}) or post them to pastebins " +"({config.pastebin_key})! Current pastebin helper: " +"{config.pastebin_helper}\n" +"Reload all modules and rerun session ({config.reimport_key}) to test out " +"changes to a module.\n" +"Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute " +"the current session when a module you've imported is modified.\n" +"\n" +"bpython -i your_script.py runs a file in interactive mode\n" +"bpython -t your_script.py pastes the contents of a file into the session\n" +"\n" +"A config file at {config.config_path} customizes keys and behavior of " +"bpython.\n" +"You can also set which pastebin helper and which external editor to use.\n" +"See {example_config_url} for an example config file.\n" +"Press {config.edit_config_key} to edit this config file.\n" +msgstr "" + From 79ff92192b18326ac3bd5441a4f7ef0aff8f7e9a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 2 Jan 2021 16:03:22 +0100 Subject: [PATCH 1139/1650] Use pathlib instead of low-level os module --- bpython/importcompletion.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index aca7e6602..c45cd0093 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -23,9 +23,9 @@ import fnmatch import importlib.machinery -import os import sys import warnings +from pathlib import Path from .line import ( current_word, @@ -129,33 +129,27 @@ def complete(self, cursor_offset, line): def find_modules(self, path): """Find all modules (and packages) for a given directory.""" - if not os.path.isdir(path): + if not path.is_dir(): # Perhaps a zip file return - basepath = os.path.basename(path) - if any(fnmatch.fnmatch(basepath, entry) for entry in self.skiplist): + if any(fnmatch.fnmatch(path.name, entry) for entry in self.skiplist): # Path is on skiplist return - try: - filenames = os.listdir(path) - except OSError: - filenames = [] - - finder = importlib.machinery.FileFinder(path) - - for name in filenames: - if any(fnmatch.fnmatch(name, entry) for entry in self.skiplist): + finder = importlib.machinery.FileFinder(str(path)) + for p in path.iterdir(): + if any(fnmatch.fnmatch(p.name, entry) for entry in self.skiplist): # Path is on skiplist continue - elif not any(name.endswith(suffix) for suffix in SUFFIXES): + elif not any(p.name.endswith(suffix) for suffix in SUFFIXES): # Possibly a package - if "." in name: + if "." in p.name: continue - elif os.path.isdir(os.path.join(path, name)): + elif p.is_dir(): # Unfortunately, CPython just crashes if there is a directory # which ends with a python extension, so work around. continue + name = p.name for suffix in SUFFIXES: if name.endswith(suffix): name = name[: -len(suffix)] @@ -183,10 +177,10 @@ def find_modules(self, path): continue else: if is_package: - path_real = os.path.realpath(pathname) + path_real = Path(pathname).resolve() if path_real not in self.paths: self.paths.add(path_real) - for subname in self.find_modules(pathname): + for subname in self.find_modules(path_real): if subname != "__init__": yield f"{name}.{subname}" yield name @@ -200,8 +194,7 @@ def find_all_modules(self, path=None): path = sys.path for p in path: - if not p: - p = os.curdir + p = Path(p).resolve() if p else Path.cwd() for module in self.find_modules(p): self.modules.add(module) yield From 38cb0b954d3baf49ea115ad5237355f37df2c417 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 2 Jan 2021 17:48:06 +0100 Subject: [PATCH 1140/1650] Exclude doc from black --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index a4df32f85..2ea7bf3e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,5 +15,6 @@ exclude = ''' | build | dist | bpython/test/fodder + | doc )/ ''' From 463b247bad2f0262870cd8b1ae750d56a37f563c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 2 Jan 2021 17:48:24 +0100 Subject: [PATCH 1141/1650] Check with black --- .github/workflows/lint.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/lint.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 000000000..db0e1ebd2 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,20 @@ +name: Linters + +on: + push: + pull_request: + +jobs: + black: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install black + - name: Check with black + run: | + black --check . From 542d6c59cecbc2059525630f8720764d091e01d1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 2 Jan 2021 17:54:30 +0100 Subject: [PATCH 1142/1650] Apply black --- bpython/curtsiesfrontend/repl.py | 6 ++++-- setup.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2d58877f4..e9965b638 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1955,7 +1955,8 @@ def help_text(self): return self.version_help_text() + "\n" + self.key_help_text() def version_help_text(self): - help_message = _(""" + help_message = _( + """ Thanks for using bpython! See http://bpython-interpreter.org/ for more information and http://docs.bpython-interpreter.org/ for docs. @@ -1975,7 +1976,8 @@ def version_help_text(self): You can also set which pastebin helper and which external editor to use. See {example_config_url} for an example config file. Press {config.edit_config_key} to edit this config file. -""").format(example_config_url=EXAMPLE_CONFIG_URL, config=self.config) +""" + ).format(example_config_url=EXAMPLE_CONFIG_URL, config=self.config) return f"bpython-curtsies version {bpython.__version__} using curtsies version {curtsies.__version__}\n{help_message}" diff --git a/setup.py b/setup.py index 64421fb31..be76c05d7 100755 --- a/setup.py +++ b/setup.py @@ -94,7 +94,13 @@ def git_describe_to_python_version(version): try: # get version from existing version file with open(version_file) as vf: - version = vf.read().strip().split("=")[-1].replace("'", "").replace("\"", "") + version = ( + vf.read() + .strip() + .split("=")[-1] + .replace("'", "") + .replace('"', "") + ) version = version.strip() except OSError: pass @@ -113,7 +119,7 @@ def git_describe_to_python_version(version): with open(version_file, "w") as vf: vf.write("# Auto-generated file, do not edit!\n") - vf.write(f"__version__ = \"{version}\"\n") + vf.write(f'__version__ = "{version}"\n') cmdclass = {"build": build} From 3f7eb13c7c20ddf796f19f4619327e19acfdf612 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 2 Jan 2021 19:24:01 +0100 Subject: [PATCH 1143/1650] Add more tests for import completion Related to #844 --- bpython/test/test_importcompletion.py | 33 ++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index ee9651a7b..ccd1ff4ba 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -15,6 +15,8 @@ def setUp(self): "zzefg", "zzabc.e", "zzabc.f", + "zzefg.a1", + "zzefg.a2" ] def test_simple_completion(self): @@ -22,12 +24,36 @@ def test_simple_completion(self): self.module_gatherer.complete(10, "import zza"), {"zzabc", "zzabd"} ) - def test_package_completion(self): + def test_import_empty(self): self.assertSetEqual( self.module_gatherer.complete(13, "import zzabc."), {"zzabc.e", "zzabc.f"}, ) + def test_import(self): + self.assertSetEqual( + self.module_gatherer.complete(14, "import zzefg.a"), + {"zzefg.a1", "zzefg.a2"}, + ) + + + @unittest.expectedFailure + def test_import_empty(self): + self.assertSetEqual( + self.module_gatherer.complete(7, "import "), {"zzabc", "zzabd", "zzefg"} + ) + + @unittest.expectedFailure + def test_from_import_empty(self): + self.assertSetEqual( + self.module_gatherer.complete(18, "from zzabc import "), {"e", "f"} + ) + + def test_from_import(self): + self.assertSetEqual( + self.module_gatherer.complete(19, "from zzefg import a"), {"a1", "a2"} + ) + class TestRealComplete(unittest.TestCase): def setUp(self): @@ -52,6 +78,11 @@ def test_from_package(self): self.module_gatherer.complete(17, "from xml import d"), {"dom"} ) + def test_from_package(self): + self.assertSetEqual( + self.module_gatherer.complete(17, "from xml import d"), {"dom"} + ) + class TestAvoidSymbolicLinks(unittest.TestCase): def setUp(self): From ee0e64b976d3665ee7591d9ac0b491c3df0c5cb9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 2 Jan 2021 19:25:04 +0100 Subject: [PATCH 1144/1650] Apply black --- bpython/test/test_importcompletion.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index ccd1ff4ba..3d3cf7fea 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -16,7 +16,7 @@ def setUp(self): "zzabc.e", "zzabc.f", "zzefg.a1", - "zzefg.a2" + "zzefg.a2", ] def test_simple_completion(self): @@ -36,11 +36,11 @@ def test_import(self): {"zzefg.a1", "zzefg.a2"}, ) - @unittest.expectedFailure def test_import_empty(self): self.assertSetEqual( - self.module_gatherer.complete(7, "import "), {"zzabc", "zzabd", "zzefg"} + self.module_gatherer.complete(7, "import "), + {"zzabc", "zzabd", "zzefg"}, ) @unittest.expectedFailure @@ -51,7 +51,8 @@ def test_from_import_empty(self): def test_from_import(self): self.assertSetEqual( - self.module_gatherer.complete(19, "from zzefg import a"), {"a1", "a2"} + self.module_gatherer.complete(19, "from zzefg import a"), + {"a1", "a2"}, ) From 8e73c8b6fd3343d6056cb6fc43b834997f4bddad Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 2 Jan 2021 22:53:40 +0100 Subject: [PATCH 1145/1650] Remove some temporary variables --- bpython/line.py | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 6f1f75e16..be040d070 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -17,11 +17,10 @@ def current_word(cursor_offset, line): """the object.attribute.attribute just before or under the cursor""" pos = cursor_offset - matches = current_word_re.finditer(line) start = pos end = pos word = None - for m in matches: + for m in current_word_re.finditer(line): if m.start(1) < pos and m.end(1) >= pos: start = m.start(1) end = m.end(1) @@ -36,8 +35,7 @@ def current_word(cursor_offset, line): def current_dict_key(cursor_offset, line): """If in dictionary completion, return the current key""" - matches = current_dict_key_re.finditer(line) - for m in matches: + for m in current_dict_key_re.finditer(line): if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -48,8 +46,7 @@ def current_dict_key(cursor_offset, line): def current_dict(cursor_offset, line): """If in dictionary completion, return the dict that should be used""" - matches = current_dict_re.finditer(line) - for m in matches: + for m in current_dict_re.finditer(line): if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -84,9 +81,8 @@ def current_object(cursor_offset, line): if match is None: return None start, end, word = match - matches = current_object_re.finditer(word) s = "" - for m in matches: + for m in current_object_re.finditer(word): if m.end(1) + start < cursor_offset: if s: s += "." @@ -132,8 +128,7 @@ def current_from_import_from(cursor_offset, line): tokens = line.split() if not ("from" in tokens or "import" in tokens): return None - matches = current_from_import_from_re.finditer(line) - for m in matches: + for m in current_from_import_from_re.finditer(line): if (m.start(1) < cursor_offset and m.end(1) >= cursor_offset) or ( m.start(2) < cursor_offset and m.end(2) >= cursor_offset ): @@ -157,8 +152,10 @@ def current_from_import_import(cursor_offset, line): match1 = current_from_import_import_re_2.search(line[baseline.end() :]) if match1 is None: return None - matches = current_from_import_import_re_3.finditer(line[baseline.end() :]) - for m in chain((match1,), matches): + for m in chain( + (match1,), + current_from_import_import_re_3.finditer(line[baseline.end() :]), + ): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: @@ -179,8 +176,9 @@ def current_import(cursor_offset, line): match1 = current_import_re_2.search(line[baseline.end() :]) if match1 is None: return None - matches = current_import_re_3.finditer(line[baseline.end() :]) - for m in chain((match1,), matches): + for m in chain( + (match1,), current_import_re_3.finditer(line[baseline.end() :]) + ): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: @@ -192,8 +190,7 @@ def current_import(cursor_offset, line): def current_method_definition_name(cursor_offset, line): """The name of a method being defined""" - matches = current_method_definition_name_re.finditer(line) - for m in matches: + for m in current_method_definition_name_re.finditer(line): if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -204,8 +201,7 @@ def current_method_definition_name(cursor_offset, line): def current_single_word(cursor_offset, line): """the un-dotted word just before or under the cursor""" - matches = current_single_word_re.finditer(line) - for m in matches: + for m in current_single_word_re.finditer(line): if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -229,8 +225,7 @@ def current_dotted_attribute(cursor_offset, line): def current_expression_attribute(cursor_offset, line): """If after a dot, the attribute being completed""" # TODO replace with more general current_expression_attribute - matches = current_expression_attribute_re.finditer(line) - for m in matches: + for m in current_expression_attribute_re.finditer(line): if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: return LinePart(m.start(1), m.end(1), m.group(1)) return None From 337173156c8899b0a8516d4de3d827be0dee88a1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 3 Jan 2021 00:10:07 +0100 Subject: [PATCH 1146/1650] Extend importcompletion tests --- bpython/test/test_importcompletion.py | 51 ++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 3d3cf7fea..9ac7d876b 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -23,18 +23,29 @@ def test_simple_completion(self): self.assertSetEqual( self.module_gatherer.complete(10, "import zza"), {"zzabc", "zzabd"} ) + self.assertSetEqual( + self.module_gatherer.complete(11, "import zza"), {"zzabc", "zzabd"} + ) def test_import_empty(self): self.assertSetEqual( self.module_gatherer.complete(13, "import zzabc."), {"zzabc.e", "zzabc.f"}, ) + self.assertSetEqual( + self.module_gatherer.complete(14, "import zzabc."), + {"zzabc.e", "zzabc.f"}, + ) def test_import(self): self.assertSetEqual( self.module_gatherer.complete(14, "import zzefg.a"), {"zzefg.a1", "zzefg.a2"}, ) + self.assertSetEqual( + self.module_gatherer.complete(15, "import zzefg.a"), + {"zzefg.a1", "zzefg.a2"}, + ) @unittest.expectedFailure def test_import_empty(self): @@ -42,18 +53,52 @@ def test_import_empty(self): self.module_gatherer.complete(7, "import "), {"zzabc", "zzabd", "zzefg"}, ) + self.assertSetEqual( + self.module_gatherer.complete(8, "import "), + {"zzabc", "zzabd", "zzefg"}, + ) @unittest.expectedFailure def test_from_import_empty(self): + self.assertSetEqual( + self.module_gatherer.complete(5, "from "), {"zzabc", "zzabd", "zzefg"} + ) + self.assertSetEqual( + self.module_gatherer.complete(6, "from "), {"zzabc", "zzabd", "zzefg"} + ) + + @unittest.expectedFailure + def test_from_module_import_empty(self): self.assertSetEqual( self.module_gatherer.complete(18, "from zzabc import "), {"e", "f"} ) + self.assertSetEqual( + self.module_gatherer.complete(19, "from zzabc import "), {"e", "f"} + ) + self.assertSetEqual( + self.module_gatherer.complete(19, "from zzabc import "), {"e", "f"} + ) + self.assertSetEqual( + self.module_gatherer.complete(19, "from zzabc import "), {"e", "f"} + ) - def test_from_import(self): + def test_from_module_import(self): self.assertSetEqual( self.module_gatherer.complete(19, "from zzefg import a"), {"a1", "a2"}, ) + self.assertSetEqual( + self.module_gatherer.complete(20, "from zzefg import a"), + {"a1", "a2"}, + ) + self.assertSetEqual( + self.module_gatherer.complete(20, "from zzefg import a"), + {"a1", "a2"}, + ) + self.assertSetEqual( + self.module_gatherer.complete(20, "from zzefg import a"), + {"a1", "a2"}, + ) class TestRealComplete(unittest.TestCase): @@ -150,3 +195,7 @@ def test_simple_symbolic_link_loop(self): filepaths.remove("Left.toRight") filepaths.remove(thing) self.assertFalse(filepaths) + + +if __name__ == "__main__": + unittest.main() From 2f956d6d542733af687ba1e23eb091d5d9d9641c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 3 Jan 2021 00:14:31 +0100 Subject: [PATCH 1147/1650] Handle more white spaces --- bpython/line.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index be040d070..2e4ac6167 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -114,7 +114,7 @@ def current_object_attribute(cursor_offset, line): current_from_import_from_re = LazyReCompile( - r"from ([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*" + r"from +([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*" ) @@ -136,9 +136,9 @@ def current_from_import_from(cursor_offset, line): return None -current_from_import_import_re_1 = LazyReCompile(r"from\s([\w0-9_.]*)\s+import") +current_from_import_import_re_1 = LazyReCompile(r"from\s+([\w0-9_.]*)\s+import") current_from_import_import_re_2 = LazyReCompile(r"([\w0-9_]+)") -current_from_import_import_re_3 = LazyReCompile(r"[,][ ]([\w0-9_]*)") +current_from_import_import_re_3 = LazyReCompile(r", *([\w0-9_]*)") def current_from_import_import(cursor_offset, line): @@ -165,7 +165,7 @@ def current_from_import_import(cursor_offset, line): current_import_re_1 = LazyReCompile(r"import") current_import_re_2 = LazyReCompile(r"([\w0-9_.]+)") -current_import_re_3 = LazyReCompile(r"[,][ ]([\w0-9_.]*)") +current_import_re_3 = LazyReCompile(r"[,][ ]*([\w0-9_.]*)") def current_import(cursor_offset, line): From 5f992edc2cef5f7bfb8a09a05326e0f78dddebdf Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 3 Jan 2021 00:26:12 +0100 Subject: [PATCH 1148/1650] Apply black --- bpython/test/test_importcompletion.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 9ac7d876b..9c01766ea 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -61,10 +61,12 @@ def test_import_empty(self): @unittest.expectedFailure def test_from_import_empty(self): self.assertSetEqual( - self.module_gatherer.complete(5, "from "), {"zzabc", "zzabd", "zzefg"} + self.module_gatherer.complete(5, "from "), + {"zzabc", "zzabd", "zzefg"}, ) self.assertSetEqual( - self.module_gatherer.complete(6, "from "), {"zzabc", "zzabd", "zzefg"} + self.module_gatherer.complete(6, "from "), + {"zzabc", "zzabd", "zzefg"}, ) @unittest.expectedFailure From 6442deaa91c33252394597724abdb8b9146be77e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 3 Jan 2021 16:23:51 +0100 Subject: [PATCH 1149/1650] Remove useless condition --- bpython/line.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 2e4ac6167..8d62d7b01 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -125,9 +125,6 @@ def current_from_import_from(cursor_offset, line): parts of an import: from (module) import (name1, name2) """ # TODO allow for as's - tokens = line.split() - if not ("from" in tokens or "import" in tokens): - return None for m in current_from_import_from_re.finditer(line): if (m.start(1) < cursor_offset and m.end(1) >= cursor_offset) or ( m.start(2) < cursor_offset and m.end(2) >= cursor_offset From cf98ab5152dd4c4ee6a597b13bb483de5688ec5a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 5 Jan 2021 22:39:11 +0100 Subject: [PATCH 1150/1650] Handle 'd' when mapping to colors (fixes #873) --- bpython/curtsiesfrontend/parse.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 5d5e661af..0c363de0e 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -3,11 +3,13 @@ from bpython.lazyre import LazyReCompile -from curtsies.termformatconstants import FG_COLORS, BG_COLORS, colors +import curtsies.termformatconstants +from curtsies.termformatconstants import FG_COLORS, BG_COLORS from curtsies.formatstring import fmtstr, FmtStr -cnames = dict(zip("krgybmcwd", colors + ("default",))) +colors = curtsies.termformatconstants.colors + ("default", ) +cnames = dict(zip("krgybmcwd", colors)) def func_for_letter(l, default="k"): From 285aa97db0542f76f3d2ae05e3be03109f2a8c4e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 5 Jan 2021 22:42:19 +0100 Subject: [PATCH 1151/1650] Reformat --- bpython/curtsiesfrontend/parse.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 0c363de0e..c783c39c6 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -1,15 +1,15 @@ -from functools import partial +import curtsies.termformatconstants import re -from bpython.lazyre import LazyReCompile - -import curtsies.termformatconstants -from curtsies.termformatconstants import FG_COLORS, BG_COLORS from curtsies.formatstring import fmtstr, FmtStr +from curtsies.termformatconstants import FG_COLORS, BG_COLORS +from functools import partial +from bpython.lazyre import LazyReCompile -colors = curtsies.termformatconstants.colors + ("default", ) -cnames = dict(zip("krgybmcwd", colors)) + +COLORS = curtsies.termformatconstants.colors + ("default",) +CNAMES = dict(zip("krgybmcwd", COLORS)) def func_for_letter(l, default="k"): @@ -18,13 +18,13 @@ def func_for_letter(l, default="k"): l = default elif l == "D": l = default.upper() - return partial(fmtstr, fg=cnames[l.lower()], bold=l.isupper()) + return partial(fmtstr, fg=CNAMES[l.lower()], bold=l.isupper()) def color_for_letter(l, default="k"): if l == "d": l = default - return cnames[l.lower()] + return CNAMES[l.lower()] def parse(s): @@ -46,23 +46,22 @@ def parse(s): def fs_from_match(d): atts = {} if d["fg"]: - # this isn't according to spec as I understand it if d["fg"].isupper(): d["bold"] = True # TODO figure out why boldness isn't based on presence of \x02 - color = cnames[d["fg"].lower()] + color = CNAMES[d["fg"].lower()] if color != "default": atts["fg"] = FG_COLORS[color] if d["bg"]: if d["bg"] == "I": # hack for finding the "inverse" - color = colors[ - (colors.index(color) + (len(colors) // 2)) % len(colors) + color = COLORS[ + (COLORS.index(color) + (len(COLORS) // 2)) % len(COLORS) ] else: - color = cnames[d["bg"].lower()] + color = CNAMES[d["bg"].lower()] if color != "default": atts["bg"] = BG_COLORS[color] if d["bold"]: From 12e9ce12f84e5b33ec94002812fb019cd6a253b3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 6 Jan 2021 09:38:31 +0100 Subject: [PATCH 1152/1650] Fix calculation of inverse color after cf98ab5152dd4c4ee6a597b13bb483de5688ec5a --- bpython/curtsiesfrontend/parse.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index c783c39c6..dacb5d6a0 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -1,15 +1,26 @@ -import curtsies.termformatconstants import re from curtsies.formatstring import fmtstr, FmtStr -from curtsies.termformatconstants import FG_COLORS, BG_COLORS +from curtsies.termformatconstants import ( + FG_COLORS, + BG_COLORS, + colors as CURTSIES_COLORS, +) from functools import partial from bpython.lazyre import LazyReCompile -COLORS = curtsies.termformatconstants.colors + ("default",) +COLORS = CURTSIES_COLORS + ("default",) CNAMES = dict(zip("krgybmcwd", COLORS)) +# hack for finding the "inverse" +INVERSE_COLORS = { + CURTSIES_COLORS[idx]: CURTSIES_COLORS[ + (idx + (len(CURTSIES_COLORS) // 2)) % len(CURTSIES_COLORS) + ] + for idx in range(len(CURTSIES_COLORS)) +} +INVERSE_COLORS["default"] = INVERSE_COLORS[CURTSIES_COLORS[0]] def func_for_letter(l, default="k"): @@ -57,9 +68,7 @@ def fs_from_match(d): if d["bg"]: if d["bg"] == "I": # hack for finding the "inverse" - color = COLORS[ - (COLORS.index(color) + (len(COLORS) // 2)) % len(COLORS) - ] + color = INVERSE_COLORS[color] else: color = CNAMES[d["bg"].lower()] if color != "default": From a82623e5a4f268c706508b72da2e7de154e91768 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 9 Jan 2021 10:50:10 +0100 Subject: [PATCH 1153/1650] Handle OSError again when iterating directories --- bpython/importcompletion.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index c45cd0093..fa2314479 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -136,8 +136,17 @@ def find_modules(self, path): # Path is on skiplist return + try: + # https://bugs.python.org/issue34541 + # Once we migrate to Python 3.8, we can change it back to directly iterator over + # path.iterdir(). + children = tuple(path.iterdir()) + except OSError: + # Path is not readable + return + finder = importlib.machinery.FileFinder(str(path)) - for p in path.iterdir(): + for p in children: if any(fnmatch.fnmatch(p.name, entry) for entry in self.skiplist): # Path is on skiplist continue From ee9b8256eb513533718fd14b8e3b2dc6733f30cf Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 9 Jan 2021 11:19:38 +0100 Subject: [PATCH 1154/1650] Store list of device ids and inodes This allows us to detect repeating paths in a more general way. --- bpython/importcompletion.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index fa2314479..3e90720f6 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -41,8 +41,7 @@ class ModuleGatherer: def __init__(self, path=None, skiplist=None): # The cached list of all known modules self.modules = set() - # List of stored paths to compare against so that real paths are not repeated - # handles symlinks not mount points + # List of (st_dev, st_ino) to compare against so that paths are not repeated self.paths = set() # Patterns to skip self.skiplist = skiplist if skiplist is not None else tuple() @@ -187,8 +186,9 @@ def find_modules(self, path): else: if is_package: path_real = Path(pathname).resolve() - if path_real not in self.paths: - self.paths.add(path_real) + stat = path_real.stat() + if (stat.st_dev, stat.st_ino) not in self.paths: + self.paths.add((stat.st_dev, stat.st_ino)) for subname in self.find_modules(path_real): if subname != "__init__": yield f"{name}.{subname}" From e4c1bd0b5a9fec2029cb36172cf9a64322de5174 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 9 Jan 2021 11:22:03 +0100 Subject: [PATCH 1155/1650] Test with pypy3 again --- .github/workflows/build.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ee822e154..ec62bca26 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,9 +10,10 @@ on: jobs: build: runs-on: ubuntu-latest + continue-on-error: ${{ matrix.python-version == 'pypy3' }} strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, pypy3] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From 66d9840d2d242efc6ebec70517c480824904eecb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 9 Jan 2021 11:36:16 +0100 Subject: [PATCH 1156/1650] Remove unused class --- bpython/test/test_autocomplete.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 5787744c0..9b0a4e86d 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -214,16 +214,6 @@ def method(self, x): pass -class OldStyleFoo: - a = 10 - - def __init__(self): - self.b = 20 - - def method(self, x): - pass - - class Properties(Foo): @property def asserts_when_called(self): From 6438d27868927895b1767fd9073675615464fa37 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 9 Jan 2021 23:42:57 +0100 Subject: [PATCH 1157/1650] Add test for #847 --- bpython/test/test_importcompletion.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 9c01766ea..1bf40e0d3 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -199,5 +199,32 @@ def test_simple_symbolic_link_loop(self): self.assertFalse(filepaths) +class TestBugReports(unittest.TestCase): + def test_issue_847(self): + with tempfile.TemporaryDirectory() as import_test_folder: + # ./xyzzy + # ./xyzzy/__init__.py + # ./xyzzy/plugh + # ./xyzzy/plugh/__init__.py + # ./xyzzy/plugh/bar.py + # ./xyzzy/plugh/foo.py + + base_path = Path(import_test_folder) + (base_path / "xyzzy" / "plugh").mkdir(parents=True) + (base_path / "xyzzy" / "__init__.py").touch() + (base_path / "xyzzy" / "plugh" / "__init__.py").touch() + (base_path / "xyzzy" / "plugh" / "bar.py").touch() + (base_path / "xyzzy" / "plugh" / "foo.py").touch() + + module_gatherer = ModuleGatherer([base_path.absolute()]) + while module_gatherer.find_coroutine(): + pass + + self.assertSetEqual( + module_gatherer.complete(17, "from xyzzy.plugh."), + {"xyzzy.plugh.bar", "xyzzy.plugh.foo"}, + ) + + if __name__ == "__main__": unittest.main() From 2c3cbd7e73c4f7be64168816be996a5daa1433e4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 10 Jan 2021 00:16:04 +0100 Subject: [PATCH 1158/1650] Specify loaders for FileFinder (fixes #847) Otherwise the FileFinder.find_spec does not know how to handle the file extensions. --- bpython/importcompletion.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 3e90720f6..077fac3e5 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -1,7 +1,7 @@ # The MIT License # # Copyright (c) 2009-2011 Andreas Stuehrk -# Copyright (c) 2020 Sebastian Ramacher +# Copyright (c) 2020-2021 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -35,6 +35,16 @@ ) SUFFIXES = importlib.machinery.all_suffixes() +LOADERS = ( + ( + importlib.machinery.ExtensionFileLoader, + importlib.machinery.EXTENSION_SUFFIXES, + ), + ( + importlib.machinery.SourceFileLoader, + importlib.machinery.SOURCE_SUFFIXES + ), +) class ModuleGatherer: @@ -144,7 +154,7 @@ def find_modules(self, path): # Path is not readable return - finder = importlib.machinery.FileFinder(str(path)) + finder = importlib.machinery.FileFinder(str(path), *LOADERS) for p in children: if any(fnmatch.fnmatch(p.name, entry) for entry in self.skiplist): # Path is on skiplist From 864b772be1e55c7eac5e3646bd6262f4480546c6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 10 Jan 2021 00:19:14 +0100 Subject: [PATCH 1159/1650] Apply black --- bpython/importcompletion.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 077fac3e5..8b0ff7bdb 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -40,10 +40,7 @@ importlib.machinery.ExtensionFileLoader, importlib.machinery.EXTENSION_SUFFIXES, ), - ( - importlib.machinery.SourceFileLoader, - importlib.machinery.SOURCE_SUFFIXES - ), + (importlib.machinery.SourceFileLoader, importlib.machinery.SOURCE_SUFFIXES), ) From fbcf8a14d3cf37ebe0e22a8ae70244bf3b447cbf Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 13 Jan 2021 22:01:54 +0100 Subject: [PATCH 1160/1650] GitHub Actions: Find typos with codespell --- .github/workflows/lint.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index db0e1ebd2..0be8a9a37 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -14,7 +14,8 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install black + pip install black codespell - name: Check with black - run: | - black --check . + run: black --check . + - name: Find typos with codespell + run: codespell --ignore-words-list="ba,te" --quiet-level=2 --skip="*.po" || true From a5011297f92ca294bab7d46db2f8bd5ceaa3fcbb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 10 Jan 2021 22:23:34 +0100 Subject: [PATCH 1161/1650] Use fstring --- bpython/test/test_crashers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 4dd80e569..f6c13c790 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -91,7 +91,7 @@ def processExited(self, reason): ( sys.executable, "-m", - "bpython." + self.backend, + f"bpython.{self.backend}", "--config", str(TEST_CONFIG), ), From 16531125e54b0be0b807034543af7810d1262879 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 10 Jan 2021 22:23:41 +0100 Subject: [PATCH 1162/1650] Reformat --- bpython/importcompletion.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 8b0ff7bdb..f298efdbd 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -40,7 +40,10 @@ importlib.machinery.ExtensionFileLoader, importlib.machinery.EXTENSION_SUFFIXES, ), - (importlib.machinery.SourceFileLoader, importlib.machinery.SOURCE_SUFFIXES), + ( + importlib.machinery.SourceFileLoader, + importlib.machinery.SOURCE_SUFFIXES, + ), ) From 1e450492d15c902ab9fbf07ab2466f27621dcc77 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2021 22:46:22 +0100 Subject: [PATCH 1163/1650] Turn codespell check into a seperate job and fail on errors --- .github/workflows/lint.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 0be8a9a37..d856a9004 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -17,5 +17,16 @@ jobs: pip install black codespell - name: Check with black run: black --check . + + codespell: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install codespell - name: Find typos with codespell - run: codespell --ignore-words-list="ba,te" --quiet-level=2 --skip="*.po" || true + run: codespell --ignore-words-list="ba,te" --quiet-level=2 --skip="*.po" From 15447b53f97626b672ded44563e45474dc2f2839 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2021 23:04:08 +0100 Subject: [PATCH 1164/1650] Fix spelling errors found by codespell --- .github/workflows/lint.yaml | 2 +- CHANGELOG.rst | 2 +- README.rst | 4 ++-- bpython/cli.py | 2 +- bpython/curtsiesfrontend/interaction.py | 2 +- bpython/sample-config | 2 +- doc/sphinx/source/configuration-options.rst | 2 +- doc/sphinx/source/django.rst | 6 +++--- doc/sphinx/source/man-bpython.rst | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d856a9004..ebc7c90f8 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -29,4 +29,4 @@ jobs: python -m pip install --upgrade pip pip install codespell - name: Find typos with codespell - run: codespell --ignore-words-list="ba,te" --quiet-level=2 --skip="*.po" + run: codespell --ignore-words-list="ba,te,deltion" --quiet-level=2 --skip="*.po" diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a9da60571..4f46096d0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -773,7 +773,7 @@ Suggestions/bug reports/patches are welcome regarding this. ----- Well, hopefully we're one step closer to making the list sizing stuff work. I really hate doing code for that kind of thing as I -never get it quite right, but with perseverence it should end up +never get it quite right, but with perseverance it should end up being completely stable; it's not the hardest thing in the world. Various cosmetic fixes have been put in at the request of a bunch diff --git a/README.rst b/README.rst index 0ff6279e5..fdb66dd88 100644 --- a/README.rst +++ b/README.rst @@ -66,7 +66,7 @@ using the package manager. Ubuntu/Debian ~~~~~~~~~~~~~ Ubuntu/Debian family Linux users and install bpython using the apt package manager, using the -command with sudo priviledge: +command with sudo privilege: .. code-block:: bash @@ -108,7 +108,7 @@ Then you should invoke a program called ``bpython-curses.exe`` instead of ``bpyt Mac OS ~~~~~~ Like Windows, Mac OS does not include a package manager by default. If you have installed any -third-party pacakge manager like MacPorts, you can install it via +third-party package manager like MacPorts, you can install it via .. code-block:: bash diff --git a/bpython/cli.py b/bpython/cli.py index 43180bb0b..6d570c613 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -524,7 +524,7 @@ def delete(self): def echo(self, s, redraw=True): """Parse and echo a formatted string with appropriate attributes. It uses the formatting method as defined in formatter.py to parse the - srings. It won't update the screen if it's reevaluating the code (as it + strings. It won't update the screen if it's reevaluating the code (as it does with undo).""" a = get_colpair(self.config, "output") if "\x01" in s: diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 7820c7c87..f7265dcc8 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -57,7 +57,7 @@ def pop_permanent_message(self, msg): if msg in self.permanent_stack: self.permanent_stack.remove(msg) else: - raise ValueError("Messsage %r was not in permanent_stack" % msg) + raise ValueError("Message %r was not in permanent_stack" % msg) @property def has_focus(self): diff --git a/bpython/sample-config b/bpython/sample-config index af11e4172..b61dce5b4 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -37,7 +37,7 @@ # External editor to use for editing the current line, block, or full history # Examples: vi (vim) -# code --wait (VS Code) - in VS Code use the command pallete to: +# code --wait (VS Code) - in VS Code use the command palette to: # Shell Command: Install 'code' command in PATH # atom -nw (Atom) # Default is to try $EDITOR and $VISUAL, then vi - but if you uncomment diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 4bc688207..2a1bbbe6c 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -402,7 +402,7 @@ yank_from_buffer ^^^^^^^^^^^^^^^^ Default: C-y -Pastes the current line from the buffer (the one you previously cutted) +Pastes the current line from the buffer (the one you previously cut) CLI --- diff --git a/doc/sphinx/source/django.rst b/doc/sphinx/source/django.rst index 443649505..c9535c4a8 100644 --- a/doc/sphinx/source/django.rst +++ b/doc/sphinx/source/django.rst @@ -12,15 +12,15 @@ out of the box models and views for a lot of stuff. For those people wanting to use bpython with their Django installation you can follow the following steps. Written by Chanita Siridechkun. The following instructions make bpython try to import a setting module in the current folder -and let django set up its enviroment with the settings module (if found) if -bpython can't find the settings module nothing happens and no enviroment gets +and let django set up its environment with the settings module (if found) if +bpython can't find the settings module nothing happens and no environment gets set up. The addition also checks if settings contains a PINAX_ROOT (if you use Pinax), if it finds this key it will do some additional Pinax setup. The Pinax addition was written by Skylar Saveland. -bpython uses something called the PYTHONSTARTUP enviroment variable. This is +bpython uses something called the PYTHONSTARTUP environment variable. This is also used by the vanilla Python REPL. Add the following lines to your ``.profile`` or equivalent file on your operating diff --git a/doc/sphinx/source/man-bpython.rst b/doc/sphinx/source/man-bpython.rst index 951300c6c..463aa6018 100644 --- a/doc/sphinx/source/man-bpython.rst +++ b/doc/sphinx/source/man-bpython.rst @@ -19,7 +19,7 @@ The idea is to provide the user with all the features in-line, much like modern IDEs, but in a simple, lightweight package that can be run in a terminal window. In-line syntax highlighting. - Hilights commands as you type! + Highlights commands as you type! Readline-like autocomplete with suggestions displayed as you type. Press tab to complete expressions when there's only one suggestion. From dfdecfc3bcc95fbd160b1c1a951b678ff51037d2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 13 Jan 2021 23:21:16 +0100 Subject: [PATCH 1165/1650] Use codespell-project/actions-codespell --- .github/workflows/lint.yaml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index ebc7c90f8..adddd5d5a 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -22,11 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install codespell - - name: Find typos with codespell - run: codespell --ignore-words-list="ba,te,deltion" --quiet-level=2 --skip="*.po" + - uses: codespell-project/actions-codespell@master + with: + skip: '*.po' + ignore_words_list: ba,te,deltion From e38c4785476df93c6400a462d68cb903f4b8525f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 Jan 2021 22:33:26 +0100 Subject: [PATCH 1166/1650] Sort imports --- bpython/curtsiesfrontend/repl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index e9965b638..01307770c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1,3 +1,4 @@ +import blessings import contextlib import errno import greenlet @@ -17,8 +18,6 @@ from wcwidth import wcswidth -import blessings - import curtsies from curtsies import FSArray, fmtstr, FmtStr, Termmode from curtsies import fmtfuncs From 275a6e7dd4b547f401c16138c0d1fc0dd2e03592 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 Jan 2021 23:03:04 +0100 Subject: [PATCH 1167/1650] Only replace importers not from six (fixes #874) This avoids the breakage seen in #874. Changes to six won't trigger a reload, however not breaking six is more important. The code now also only provides some functions of the underling finder/importer also does. --- bpython/curtsiesfrontend/repl.py | 85 ++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 01307770c..8f5d9ddc1 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -212,7 +212,20 @@ def __init__(self, watcher, loader): self.watcher = watcher self.loader = loader - def load_module(self, name): + def __getattr__(self, name): + if name == "create_module" and hasattr(self.loader, name): + return self._create_module + if name == "load_module" and hasattr(self.loader, name): + return self._load_module + return getattr(self.loader, name) + + def _create_module(self, spec): + spec = self.loader.create_module(spec) + if getattr(spec, "origin", None) is not None and spec.origin != "builtin": + self.watcher.track_module(spec.origin) + return spec + + def _load_module(self, name): module = self.loader.load_module(name) if hasattr(module, "__file__"): self.watcher.track_module(module.__file__) @@ -220,42 +233,30 @@ def load_module(self, name): class ImportFinder: - def __init__(self, watcher, old_meta_path): + def __init__(self, finder, watcher): self.watcher = watcher - self.old_meta_path = old_meta_path - - def find_distributions(self, context): - for finder in self.old_meta_path: - distribution_finder = getattr(finder, "find_distributions", None) - if distribution_finder is not None: - loader = finder.find_distributions(context) - if loader is not None: - return loader - - return None - - def find_spec(self, fullname, path, target=None): - for finder in self.old_meta_path: - # Consider the finder only if it implements find_spec - if getattr(finder, "find_spec", None) is None: - continue - # Attempt to find the spec - spec = finder.find_spec(fullname, path, target) - if spec is not None: - if getattr(spec, "__loader__", None) is not None: - # Patch the loader to enable reloading - spec.__loader__ = ImportLoader( - self.watcher, spec.__loader__ - ) - return spec - - def find_module(self, fullname, path=None): - for finder in self.old_meta_path: - loader = finder.find_module(fullname, path) - if loader is not None: - return ImportLoader(self.watcher, loader) - - return None + self.finder = finder + + def __getattr__(self, name): + if name == "find_spec" and hasattr(self.finder, name): + return self._find_spec + if name == "find_module" and hasattr(self.finder, name): + return self._find_module + return getattr(self.finder, name) + + def _find_spec(self, fullname, path, target=None): + # Attempt to find the spec + spec = self.finder.find_spec(fullname, path, target) + if spec is not None: + if getattr(spec, "__loader__", None) is not None: + # Patch the loader to enable reloading + spec.__loader__ = ImportLoader(self.watcher, spec.__loader__) + return spec + + def _find_module(self, fullname, path=None): + loader = self.finder.find_module(fullname, path) + if loader is not None: + return ImportLoader(self.watcher, loader) class BaseRepl(BpythonRepl): @@ -531,7 +532,17 @@ def __enter__(self): self.orig_meta_path = sys.meta_path if self.watcher: - sys.meta_path = [ImportFinder(self.watcher, self.orig_meta_path)] + meta_path = [] + for finder in sys.meta_path: + # All elements get wrapped in ImportFinder instances execepted for instances of + # _SixMetaPathImporter (from six). When importing six, it will check if the importer + # is already part of sys.meta_path and will remove instances. We do not want to + # break this feature (see also #874). + if type(finder).__name__ == "_SixMetaPathImporter": + meta_path.append(finder) + else: + meta_path.append(ImportFinder(finder, self.watcher)) + sys.meta_path = meta_path sitefix.monkeypatch_quit() return self From f74ad98d641f986b35f43be1d972b4e1b76b8cba Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 Jan 2021 23:06:58 +0100 Subject: [PATCH 1168/1650] Formatting --- bpython/curtsiesfrontend/repl.py | 5 ++++- bpython/repl.py | 4 +--- bpython/test/test_crashers.py | 5 ++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 8f5d9ddc1..9bb9edc94 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -221,7 +221,10 @@ def __getattr__(self, name): def _create_module(self, spec): spec = self.loader.create_module(spec) - if getattr(spec, "origin", None) is not None and spec.origin != "builtin": + if ( + getattr(spec, "origin", None) is not None + and spec.origin != "builtin" + ): self.watcher.track_module(spec.origin) return spec diff --git a/bpython/repl.py b/bpython/repl.py index 9939d7808..21f5942e7 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -130,9 +130,7 @@ def runsource(self, source, filename=None, symbol="single", encode="auto"): files should always already have an encoding comment or be ASCII. By default an encoding line will be added if no filename is given. - In Python 3, source must be a unicode string - In Python 2, source may be latin-1 bytestring or unicode string, - following the interface of code.InteractiveInterpreter. + source must be a string Because adding an encoding comment to a unicode string in Python 2 would cause a syntax error to be thrown which would reference code diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index f6c13c790..8988518e2 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -95,7 +95,10 @@ def processExited(self, reason): "--config", str(TEST_CONFIG), ), - env=dict(TERM="vt100", LANG=os.environ.get("LANG", "")), + env={ + "TERM": "vt100", + "LANG": os.environ.get("LANG", ""), + }, usePTY=(master, slave, os.ttyname(slave)), ) return result From be1a6de5ea5e3a06d8f01487d8809aaa24ee9c0c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 Jan 2021 23:16:44 +0100 Subject: [PATCH 1169/1650] Add some docstrings --- bpython/curtsiesfrontend/repl.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9bb9edc94..c5b12ef25 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -208,6 +208,8 @@ def readline(self): class ImportLoader: + """Wrapper for module loaders to watch their paths with watchdog.""" + def __init__(self, watcher, loader): self.watcher = watcher self.loader = loader @@ -236,6 +238,8 @@ def _load_module(self, name): class ImportFinder: + """Wrapper for finders in sys.meta_path to replace wrap all loaders with ImportLoader.""" + def __init__(self, finder, watcher): self.watcher = watcher self.finder = finder From e8b8c686182bd7e75a7f54a92c818344327b5254 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 Jan 2021 23:27:23 +0100 Subject: [PATCH 1170/1650] Re-arrange imports --- bpython/curtsiesfrontend/repl.py | 47 ++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c5b12ef25..1da6f5ebe 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -18,40 +18,45 @@ from wcwidth import wcswidth -import curtsies -from curtsies import FSArray, fmtstr, FmtStr, Termmode -from curtsies import fmtfuncs -from curtsies import events - -import bpython -from bpython.repl import Repl as BpythonRepl, SourceNotFound -from bpython.repl import LineTypeTranslator as LineType +from curtsies import ( + FSArray, + fmtstr, + FmtStr, + Termmode, + fmtfuncs, + events, + __version__ as curtsies_version, +) +from curtsies.configfile_keynames import keymap as key_dispatch + +from bpython import __version__ +from bpython.repl import ( + Repl as BpythonRepl, + SourceNotFound, + LineTypeTranslator as LineType, +) from bpython.config import getpreferredencoding from bpython.formatter import BPythonFormatter from bpython import autocomplete from bpython.translations import _ from bpython.pager import get_pager_command -from bpython.curtsiesfrontend import replpainter as paint -from bpython.curtsiesfrontend import sitefix -from bpython.curtsiesfrontend.coderunner import ( +from . import events as bpythonevents, sitefix, replpainter as paint +from .coderunner import ( CodeRunner, FakeOutput, is_main_thread, ) -from bpython.curtsiesfrontend.filewatch import ModuleChangedEventHandler -from bpython.curtsiesfrontend.interaction import StatusBar -from bpython.curtsiesfrontend.manual_readline import edit_keys -from bpython.curtsiesfrontend import events as bpythonevents -from bpython.curtsiesfrontend.parse import parse as bpythonparse -from bpython.curtsiesfrontend.parse import func_for_letter, color_for_letter -from bpython.curtsiesfrontend.preprocess import preprocess -from bpython.curtsiesfrontend.interpreter import ( +from .filewatch import ModuleChangedEventHandler +from .interaction import StatusBar +from .manual_readline import edit_keys +from .parse import parse as bpythonparse, func_for_letter, color_for_letter +from .preprocess import preprocess +from .interpreter import ( Interp, code_finished_will_parse, ) -from curtsies.configfile_keynames import keymap as key_dispatch logger = logging.getLogger(__name__) @@ -1996,7 +2001,7 @@ def version_help_text(self): """ ).format(example_config_url=EXAMPLE_CONFIG_URL, config=self.config) - return f"bpython-curtsies version {bpython.__version__} using curtsies version {curtsies.__version__}\n{help_message}" + return f"bpython-curtsies version {__version__} using curtsies version {curtsies_version}\n{help_message}" def key_help_text(self): NOT_IMPLEMENTED = ( From d412b5e9a790e10fce8bb1267efb9301e1aad439 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 Jan 2021 23:35:36 +0100 Subject: [PATCH 1171/1650] Update changelog --- CHANGELOG.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4f46096d0..bc6b3a921 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,10 +11,16 @@ General information: New features: * #643: Provide bpython._version if built from Github tarballs +* #849: Make import completion skip list configurable Fixes: +* #847: Fix import completion of modles * #857: Replace remaining use of deprecated imp with importlib +* #866: Add more directories to the default import completion skip list +* #873: Handle 'd' when mapping colors +* #874: Avoid breakage with six's importer + 0.20.1 ------ From 93b813326fe5465384b8d7930f9a5bc8e88517d8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 16 Jan 2021 23:39:53 +0100 Subject: [PATCH 1172/1650] List more fixes and new features --- CHANGELOG.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bc6b3a921..d6e3fe88c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,16 +12,21 @@ New features: * #643: Provide bpython._version if built from Github tarballs * #849: Make import completion skip list configurable +* #876: Check spelling with codespell + Thanks to Christian Clauss Fixes: * #847: Fix import completion of modles * #857: Replace remaining use of deprecated imp with importlib +* #862: Upgrade curtsies version requirements + Thanks to Kelsey Blair +* #863: State correct default config file directory + Thanks to niloct * #866: Add more directories to the default import completion skip list * #873: Handle 'd' when mapping colors * #874: Avoid breakage with six's importer - 0.20.1 ------ From 1e80189b68d22a48e549b1256653b3e1f19e5e96 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 22 Jan 2021 16:10:54 +0100 Subject: [PATCH 1173/1650] Set supported Python versions in setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index be76c05d7..9a657fcba 100755 --- a/setup.py +++ b/setup.py @@ -225,6 +225,7 @@ def git_describe_to_python_version(version): long_description="""bpython is a fancy interface to the Python interpreter for Unix-like operating systems.""", classifiers=classifiers, + python_requires=">=3.6", install_requires=install_requires, extras_require=extras_require, packages=packages, From e248821bc249a0c845f1e1cb80c0d37b42d80b85 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 Jan 2021 10:21:41 +0100 Subject: [PATCH 1174/1650] Use is_main_thread from curtsies.input --- bpython/curtsiesfrontend/coderunner.py | 7 ++----- bpython/curtsiesfrontend/repl.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index 8baabf35c..cddf1169d 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -14,13 +14,10 @@ import greenlet import logging import signal -import threading - -logger = logging.getLogger(__name__) +from curtsies.input import is_main_thread -def is_main_thread(): - return threading.main_thread() == threading.current_thread() +logger = logging.getLogger(__name__) class SigintHappened: diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 1da6f5ebe..24ce7cc87 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -28,6 +28,7 @@ __version__ as curtsies_version, ) from curtsies.configfile_keynames import keymap as key_dispatch +from curtsies.input import is_main_thread from bpython import __version__ from bpython.repl import ( @@ -45,7 +46,6 @@ from .coderunner import ( CodeRunner, FakeOutput, - is_main_thread, ) from .filewatch import ModuleChangedEventHandler from .interaction import StatusBar From 3510efcd599ccca066a6a9c0a84f5504d54608a9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 17 Jan 2021 12:00:13 +0100 Subject: [PATCH 1175/1650] Use super --- bpython/repl.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 21f5942e7..7c827602a 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -110,9 +110,7 @@ def __init__(self, locals=None, encoding=None): sys.modules["__main__"] = main_mod = ModuleType("__main__") locals = main_mod.__dict__ - # Unfortunately code.InteractiveInterpreter is a classic class, so no - # super() - code.InteractiveInterpreter.__init__(self, locals) + super().__init__(locals) self.timer = RuntimeTimer() def reset_running_time(self): From 7a6752343950011e18303e26bb52769679d33c74 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 14:59:20 +0100 Subject: [PATCH 1176/1650] Store setting of unicode_box --- bpython/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index 4b7f33a45..5af10d1dd 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -288,6 +288,7 @@ def get_key_no_doublebind(command): struct.curtsies_right_arrow_completion = config.getboolean( "curtsies", "right_arrow_completion" ) + struct.unicode_box = config.getboolean("general", "unicode_box") color_scheme_name = config.get("general", "color_scheme") @@ -332,7 +333,7 @@ def get_key_no_doublebind(command): struct.autocomplete_mode = default_completion # set box drawing characters - if config.getboolean("general", "unicode_box") and supports_box_chars(): + if struct.unicode_box and supports_box_chars(): struct.left_border = "│" struct.right_border = "│" struct.top_border = "─" From dc23149d4270dc5187d7121f2ffc831cdd5597b8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 14:59:48 +0100 Subject: [PATCH 1177/1650] Assert cursor_row >= 0 --- bpython/curtsiesfrontend/repl.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 24ce7cc87..17ecc7419 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1576,7 +1576,7 @@ def move_screen_up(current_line_start_row): + wcswidth(self.stdin.current_line[: self.stdin.cursor_offset]), width, ) - assert cursor_column >= 0, cursor_column + assert cursor_row >= 0 and cursor_column >= 0, (cursor_row, cursor_column) elif self.coderunner.running: # TODO does this ever happen? cursor_row, cursor_column = divmod( ( @@ -1585,7 +1585,8 @@ def move_screen_up(current_line_start_row): ), width, ) - assert cursor_column >= 0, ( + assert cursor_row >= 0 and cursor_column >= 0, ( + cursor_row, cursor_column, len(self.current_cursor_line), len(self.current_line), @@ -1601,7 +1602,8 @@ def move_screen_up(current_line_start_row): + self.number_of_padding_chars_on_current_cursor_line(), width, ) - assert cursor_column >= 0, ( + assert cursor_row >= 0 and cursor_column >= 0, ( + cursor_row, cursor_column, len(self.current_cursor_line), len(self.current_line), From 4364fc9c7aacd16e9f4fada884c72beea4823fc6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 15:01:00 +0100 Subject: [PATCH 1178/1650] Fix handling of box characters in tests --- bpython/test/test_curtsies_painting.py | 187 ++++++++++++++----------- 1 file changed, 102 insertions(+), 85 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index fb82627ee..6cb978f5c 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -65,6 +65,18 @@ def assert_paint_ignoring_formatting( if cursor_row_col is not None: self.assertEqual(cursor_pos, cursor_row_col) + def process_box_characters(self, screen): + if not self.repl.config.unicode_box or not config.supports_box_chars(): + return [ + line.replace("┌", "+") + .replace("└", "+") + .replace("┘", "+") + .replace("┐", "+") + .replace("─", "-") + for line in screen + ] + return screen + class TestCurtsiesPaintingTest(CurtsiesPaintingTest): def test_history_is_cleared(self): @@ -108,22 +120,15 @@ def test_completion(self): self.repl.height, self.repl.width = (5, 32) self.repl.current_line = "an" self.cursor_offset = 2 - if config.supports_box_chars(): - screen = [ + screen = self.process_box_characters( + [ ">>> an", "┌──────────────────────────────┐", "│ and any( │", "└──────────────────────────────┘", "Welcome to bpython! Press f", ] - else: - screen = [ - ">>> an", - "+------------------------------+", - "| and any( |", - "+------------------------------+", - "Welcome to bpython! Press f", - ] + ) self.assert_paint_ignoring_formatting(screen, (0, 4)) def test_argspec(self): @@ -729,14 +734,16 @@ def test_simple(self): self.repl.current_line = "abc" self.repl.cursor_offset = 3 self.repl.process_event(".") - screen = [ - ">>> abc.", - "+------------------+", - "| aaaaaaaaaaaaaaaa |", - "| b |", - "| c |", - "+------------------+", - ] + screen = self.process_box_characters( + [ + ">>> abc.", + "┌──────────────────┐", + "│ aaaaaaaaaaaaaaaa │", + "│ b │", + "│ c │", + "└──────────────────┘", + ] + ) self.assert_paint_ignoring_formatting(screen, (0, 8)) def test_fill_screen(self): @@ -745,23 +752,25 @@ def test_fill_screen(self): self.repl.current_line = "abc" self.repl.cursor_offset = 3 self.repl.process_event(".") - screen = [ - ">>> abc.", - "+------------------+", - "| aaaaaaaaaaaaaaaa |", - "| b |", - "| c |", - "| d |", - "| e |", - "| f |", - "| g |", - "| h |", - "| i |", - "| j |", - "| k |", - "| l |", - "+------------------+", - ] + screen = self.process_box_characters( + [ + ">>> abc.", + "┌──────────────────┐", + "│ aaaaaaaaaaaaaaaa │", + "│ b │", + "│ c │", + "│ d │", + "│ e │", + "│ f │", + "│ g │", + "│ h │", + "│ i │", + "│ j │", + "│ k │", + "│ l │", + "└──────────────────┘", + ] + ) self.assert_paint_ignoring_formatting(screen, (0, 8)) def test_lower_on_screen(self): @@ -771,37 +780,41 @@ def test_lower_on_screen(self): self.repl.current_line = "abc" self.repl.cursor_offset = 3 self.repl.process_event(".") - screen = [ - ">>> abc.", - "+------------------+", - "| aaaaaaaaaaaaaaaa |", - "| b |", - "| c |", - "| d |", - "| e |", - "| f |", - "| g |", - "| h |", - "| i |", - "| j |", - "| k |", - "| l |", - "+------------------+", - ] + screen = self.process_box_characters( + [ + ">>> abc.", + "┌──────────────────┐", + "│ aaaaaaaaaaaaaaaa │", + "│ b │", + "│ c │", + "│ d │", + "│ e │", + "│ f │", + "│ g │", + "│ h │", + "│ i │", + "│ j │", + "│ k │", + "│ l │", + "└──────────────────┘", + ] + ) # behavior before issue #466 self.assert_paint_ignoring_formatting( screen, try_preserve_history_height=0 ) self.assert_paint_ignoring_formatting(screen, min_infobox_height=100) # behavior after issue #466 - screen = [ - ">>> abc.", - "+------------------+", - "| aaaaaaaaaaaaaaaa |", - "| b |", - "| c |", - "+------------------+", - ] + screen = self.process_box_characters( + [ + ">>> abc.", + "┌──────────────────┐", + "│ aaaaaaaaaaaaaaaa │", + "│ b │", + "│ c │", + "└──────────────────┘", + ] + ) self.assert_paint_ignoring_formatting(screen) def test_at_bottom_of_screen(self): @@ -811,35 +824,39 @@ def test_at_bottom_of_screen(self): self.repl.current_line = "abc" self.repl.cursor_offset = 3 self.repl.process_event(".") - screen = [ - ">>> abc.", - "+------------------+", - "| aaaaaaaaaaaaaaaa |", - "| b |", - "| c |", - "| d |", - "| e |", - "| f |", - "| g |", - "| h |", - "| i |", - "| j |", - "| k |", - "| l |", - "+------------------+", - ] + screen = self.process_box_characters( + [ + ">>> abc.", + "┌──────────────────┐", + "│ aaaaaaaaaaaaaaaa │", + "│ b │", + "│ c │", + "│ d │", + "│ e │", + "│ f │", + "│ g │", + "│ h │", + "│ i │", + "│ j │", + "│ k │", + "│ l │", + "└──────────────────┘", + ] + ) # behavior before issue #466 self.assert_paint_ignoring_formatting( screen, try_preserve_history_height=0 ) self.assert_paint_ignoring_formatting(screen, min_infobox_height=100) # behavior after issue #466 - screen = [ - ">>> abc.", - "+------------------+", - "| aaaaaaaaaaaaaaaa |", - "| b |", - "| c |", - "+------------------+", - ] + screen = self.process_box_characters( + [ + ">>> abc.", + "┌──────────────────┐", + "│ aaaaaaaaaaaaaaaa │", + "│ b │", + "│ c │", + "└──────────────────┘", + ] + ) self.assert_paint_ignoring_formatting(screen) From fa4377734bbd4e9529a49fc0d60cb6266e9133cc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 15:01:25 +0100 Subject: [PATCH 1179/1650] The tests require unicode, so set LANG and LC_ALL to C.UTF-8 --- bpython/test/test_curtsies_painting.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 6cb978f5c..ead71178f 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -29,7 +29,9 @@ def setup_config(): class ClearEnviron(TestCase): @classmethod def setUpClass(cls): - cls.mock_environ = mock.patch.dict("os.environ", {}, clear=True) + cls.mock_environ = mock.patch.dict( + "os.environ", {"LC_LANG": "C.UTF-8", "LANG": "C.UTF-8"}, clear=True + ) cls.mock_environ.start() TestCase.setUpClass() From 5f86b6bf9d409dfeb39569e03bd6e53746ab3b1a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 23 Jan 2021 16:29:45 +0100 Subject: [PATCH 1180/1650] Use cwcwidth --- bpython/curtsiesfrontend/repl.py | 2 +- requirements.txt | 1 + setup.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 17ecc7419..92f3ed2e3 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -16,7 +16,7 @@ from pygments.lexers import Python3Lexer from pygments.formatters import TerminalFormatter -from wcwidth import wcswidth +from cwcwidth import wcswidth from curtsies import ( FSArray, diff --git a/requirements.txt b/requirements.txt index 4a9e91bca..7ec4a6113 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ Pygments curtsies >=0.3.3 +cwcwidth greenlet pyxdg requests diff --git a/setup.py b/setup.py index 9a657fcba..70a6bf6b4 100755 --- a/setup.py +++ b/setup.py @@ -179,7 +179,7 @@ def git_describe_to_python_version(version): "requests", "curtsies >=0.3.3", "greenlet", - "wcwidth", + "cwcwidth", "pyxdg", ] From ad65967de3b82d8d21b0917276d62d6b83f45a45 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 15:10:08 +0100 Subject: [PATCH 1181/1650] Specify max length instead of splicing --- bpython/curtsiesfrontend/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 92f3ed2e3..4d6328433 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1573,7 +1573,7 @@ def move_screen_up(current_line_start_row): if self.stdin.has_focus: cursor_row, cursor_column = divmod( wcswidth(self.current_stdouterr_line) - + wcswidth(self.stdin.current_line[: self.stdin.cursor_offset]), + + wcswidth(self.stdin.current_line, self.stdin.cursor_offset), width, ) assert cursor_row >= 0 and cursor_column >= 0, (cursor_row, cursor_column) @@ -1597,7 +1597,7 @@ def move_screen_up(current_line_start_row): ( wcswidth(self.current_cursor_line_without_suggestion.s) - wcswidth(self.current_line) - + wcswidth(self.current_line[: self.cursor_offset]) + + wcswidth(self.current_line, self.cursor_offset) ) + self.number_of_padding_chars_on_current_cursor_line(), width, From b522be41edf9591b64411466975ec43d1aae3799 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 15:19:03 +0100 Subject: [PATCH 1182/1650] Apply black --- bpython/curtsiesfrontend/repl.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 4d6328433..143b916a5 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1576,7 +1576,10 @@ def move_screen_up(current_line_start_row): + wcswidth(self.stdin.current_line, self.stdin.cursor_offset), width, ) - assert cursor_row >= 0 and cursor_column >= 0, (cursor_row, cursor_column) + assert cursor_row >= 0 and cursor_column >= 0, ( + cursor_row, + cursor_column, + ) elif self.coderunner.running: # TODO does this ever happen? cursor_row, cursor_column = divmod( ( From 80d7d9407259525915dad86d79d079de0d9fc052 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 16:32:20 +0100 Subject: [PATCH 1183/1650] Use new assert helper functions from curtsies --- README.rst | 2 +- bpython/test/test_curtsies_painting.py | 22 +++++++++++++--------- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index fdb66dd88..54d2e7d86 100644 --- a/README.rst +++ b/README.rst @@ -155,7 +155,7 @@ your config file as **~/.config/bpython/config** (i.e. Dependencies ============ * Pygments -* curtsies >= 0.3.3 +* curtsies >= 0.3.5 * greenlet * pyxdg * requests diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index ead71178f..c8152a3cd 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -4,7 +4,11 @@ import sys from contextlib import contextmanager -from curtsies.formatstringarray import FormatStringTest, fsarray +from curtsies.formatstringarray import ( + fsarray, + assertFSArraysEqual, + assertFSArraysEqualIgnoringFormatting, +) from curtsies.fmtfuncs import cyan, bold, green, yellow, on_magenta, red from unittest import mock @@ -41,7 +45,7 @@ def tearDownClass(cls): TestCase.tearDownClass() -class CurtsiesPaintingTest(FormatStringTest, ClearEnviron): +class CurtsiesPaintingTest(ClearEnviron): def setUp(self): class TestRepl(BaseRepl): def _request_refresh(inner_self): @@ -56,14 +60,14 @@ def locals(self): def assert_paint(self, screen, cursor_row_col): array, cursor_pos = self.repl.paint() - self.assertFSArraysEqual(array, screen) + assertFSArraysEqual(array, screen) self.assertEqual(cursor_pos, cursor_row_col) def assert_paint_ignoring_formatting( self, screen, cursor_row_col=None, **paint_kwargs ): array, cursor_pos = self.repl.paint(**paint_kwargs) - self.assertFSArraysEqualIgnoringFormatting(array, screen) + assertFSArraysEqualIgnoringFormatting(array, screen) if cursor_row_col is not None: self.assertEqual(cursor_pos, cursor_row_col) @@ -156,7 +160,7 @@ def foo(x, y, z=10): + bold(cyan("10")) + yellow(")") ] - self.assertFSArraysEqual(fsarray(array), fsarray(screen)) + assertFSArraysEqual(fsarray(array), fsarray(screen)) def test_formatted_docstring(self): actual = replpainter.formatted_docstring( @@ -165,7 +169,7 @@ def test_formatted_docstring(self): config=setup_config(), ) expected = fsarray(["Returns the results", "", "Also has side effects"]) - self.assertFSArraysEqualIgnoringFormatting(actual, expected) + assertFSArraysEqualIgnoringFormatting(actual, expected) def test_unicode_docstrings(self): "A bit of a special case in Python 2" @@ -178,7 +182,7 @@ def foo(): foo.__doc__, 40, config=setup_config() ) expected = fsarray(["åß∂ƒ"]) - self.assertFSArraysEqualIgnoringFormatting(actual, expected) + assertFSArraysEqualIgnoringFormatting(actual, expected) def test_nonsense_docstrings(self): for docstring in [ @@ -208,7 +212,7 @@ def foo(): wd = pydoc.getdoc(foo) actual = replpainter.formatted_docstring(wd, 40, config=setup_config()) expected = fsarray(["asdfåß∂ƒ"]) - self.assertFSArraysEqualIgnoringFormatting(actual, expected) + assertFSArraysEqualIgnoringFormatting(actual, expected) def test_paint_lasts_events(self): actual = replpainter.paint_last_events( @@ -219,7 +223,7 @@ def test_paint_lasts_events(self): else: expected = fsarray(["+-+", "|c|", "|b|", "+-+"]) - self.assertFSArraysEqualIgnoringFormatting(actual, expected) + assertFSArraysEqualIgnoringFormatting(actual, expected) @contextmanager diff --git a/requirements.txt b/requirements.txt index 7ec4a6113..bcdb090be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Pygments -curtsies >=0.3.3 +curtsies >=0.3.5 cwcwidth greenlet pyxdg diff --git a/setup.py b/setup.py index 70a6bf6b4..f73ecf904 100755 --- a/setup.py +++ b/setup.py @@ -177,7 +177,7 @@ def git_describe_to_python_version(version): install_requires = [ "pygments", "requests", - "curtsies >=0.3.3", + "curtsies >=0.3.5", "greenlet", "cwcwidth", "pyxdg", From 1b0f48688486b880e1d06fa5ddfcd026c70915b3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 17:11:05 +0100 Subject: [PATCH 1184/1650] Specify width --- bpython/test/test_curtsies_painting.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index c8152a3cd..d5a9f3b33 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -641,6 +641,7 @@ def test_cursor_stays_at_bottom_of_screen(self): def test_unhighlight_paren_bugs(self): """two previous bugs, parent didn't highlight until next render and paren didn't unhighlight until enter""" + self.repl.width = 32 self.assertEqual(self.repl.rl_history.entries, [""]) self.enter("(") self.assertEqual(self.repl.rl_history.entries, [""]) @@ -657,7 +658,8 @@ def test_unhighlight_paren_bugs(self): [ cyan(">>> ") + on_magenta(bold(red("("))), green("... ") + on_magenta(bold(red(")"))), - ] + ], + width=32 ) self.assert_paint(screen, (1, 5)) @@ -667,7 +669,8 @@ def test_unhighlight_paren_bugs(self): [ cyan(">>> ") + yellow("("), green("... ") + yellow(")") + bold(cyan(" ")), - ] + ], + width=32 ) self.assert_paint(screen, (1, 6)) From 08024defa4ffc479e0298dfbe5d08b85e5d4e96b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 17:13:28 +0100 Subject: [PATCH 1185/1650] Update CHANGELOG --- CHANGELOG.rst | 6 ++++++ bpython/test/test_curtsies_painting.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d6e3fe88c..4a0448347 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,6 +27,12 @@ Fixes: * #873: Handle 'd' when mapping colors * #874: Avoid breakage with six's importer +Changes to dependencies: + +* curtsies >= 0.3.5 is now required +* pyxdg is now required +* wcwidth has been replaced with cwcwidth + 0.20.1 ------ diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index d5a9f3b33..e3970a089 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -659,7 +659,7 @@ def test_unhighlight_paren_bugs(self): cyan(">>> ") + on_magenta(bold(red("("))), green("... ") + on_magenta(bold(red(")"))), ], - width=32 + width=32, ) self.assert_paint(screen, (1, 5)) @@ -670,7 +670,7 @@ def test_unhighlight_paren_bugs(self): cyan(">>> ") + yellow("("), green("... ") + yellow(")") + bold(cyan(" ")), ], - width=32 + width=32, ) self.assert_paint(screen, (1, 6)) From b1082cfb7adfa022b9d38bed68496064a60e874e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 18:23:33 +0100 Subject: [PATCH 1186/1650] Only set LC_ALL and LANG in test suite if not set --- bpython/test/test_crashers.py | 2 +- bpython/test/test_curtsies_painting.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 8988518e2..f9afe7947 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -97,7 +97,7 @@ def processExited(self, reason): ), env={ "TERM": "vt100", - "LANG": os.environ.get("LANG", ""), + "LANG": os.environ.get("LANG", "C.UTF-8"), }, usePTY=(master, slave, os.ttyname(slave)), ) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index e3970a089..7ce91d67a 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -1,4 +1,5 @@ import itertools +import os import pydoc import string import sys @@ -34,7 +35,12 @@ class ClearEnviron(TestCase): @classmethod def setUpClass(cls): cls.mock_environ = mock.patch.dict( - "os.environ", {"LC_LANG": "C.UTF-8", "LANG": "C.UTF-8"}, clear=True + "os.environ", + { + "LC_ALL": os.environ.get("LC_ALL", "C.UTF-8"), + "LANG": os.environ.get("LANG", "C.UTF-8"), + }, + clear=True, ) cls.mock_environ.start() TestCase.setUpClass() From cb66a009bc7de7c3f3bf68ab93023432e5b28f9b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 21:25:00 +0100 Subject: [PATCH 1187/1650] Ensure that passed length is non-negative --- bpython/curtsiesfrontend/repl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 143b916a5..86c2cb1d0 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1573,7 +1573,9 @@ def move_screen_up(current_line_start_row): if self.stdin.has_focus: cursor_row, cursor_column = divmod( wcswidth(self.current_stdouterr_line) - + wcswidth(self.stdin.current_line, self.stdin.cursor_offset), + + wcswidth( + self.stdin.current_line, max(0, self.stdin.cursor_offset) + ), width, ) assert cursor_row >= 0 and cursor_column >= 0, ( @@ -1600,7 +1602,7 @@ def move_screen_up(current_line_start_row): ( wcswidth(self.current_cursor_line_without_suggestion.s) - wcswidth(self.current_line) - + wcswidth(self.current_line, self.cursor_offset) + + wcswidth(self.current_line, max(0, self.cursor_offset)) ) + self.number_of_padding_chars_on_current_cursor_line(), width, From f4c0d3428731e48cf84d145c3e8f72212eab323b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 21:39:51 +0100 Subject: [PATCH 1188/1650] Run code directly via Python instead of InteractiveInterpreter We cannot do anything reasonable here anyway, so just let Python handle this case. --- bpython/args.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 0636dad9f..e877ac1c1 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -25,7 +25,6 @@ Module to handle command line argument parsing, for all front-ends. """ -import code import importlib.util import os import sys @@ -145,9 +144,8 @@ def callback(group): raise SystemExit if not ignore_stdin and not (sys.stdin.isatty() and sys.stdout.isatty()): - interpreter = code.InteractiveInterpreter() - interpreter.runsource(sys.stdin.read()) - raise SystemExit + # Just let Python handle this + os.execv(sys.executable, [sys.executable] + args) config = Struct() loadini(config, options.config) From 35ed73aebb2aa4b6fc8db5392da0359adb50938b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 21:46:35 +0100 Subject: [PATCH 1189/1650] Reenable TestExecArgs tests --- bpython/test/test_args.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 2115bc088..b536292f1 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -10,7 +10,9 @@ class TestExecArgs(unittest.TestCase): - @unittest.skip("test broken under pytest") + # These tests are currently not very useful. Under pytest neither stdout nor stdin are ttys, + # hence bpython.args.parse will just exectute code by falling back to Python. + def test_exec_dunder_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write( @@ -25,7 +27,7 @@ def test_exec_dunder_file(self): p = subprocess.Popen( [sys.executable] + ["-m", "bpython.curtsies", f.name], stderr=subprocess.PIPE, - universal_newlines=True, + text=True, ) (_, stderr) = p.communicate() @@ -36,7 +38,6 @@ def test_exec_nonascii_file(self): f.write( dedent( """\ - #!/usr/bin/env python # coding: utf-8 "你好 # nonascii" """ @@ -50,7 +51,6 @@ def test_exec_nonascii_file(self): except subprocess.CalledProcessError: self.fail("Error running module with nonascii characters") - @unittest.skip("test broken under pytest") def test_exec_nonascii_file_linenums(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write( @@ -66,7 +66,7 @@ def test_exec_nonascii_file_linenums(self): p = subprocess.Popen( [sys.executable, "-m", "bpython.curtsies", f.name], stderr=subprocess.PIPE, - universal_newlines=True, + text=True, ) (_, stderr) = p.communicate() From ee3b0e992e69f8489c2fbec019790c1f3b0761e4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 21:53:58 +0100 Subject: [PATCH 1190/1650] Revert to universal_newlines text was only introduced in Python 3.7. --- bpython/test/test_args.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index b536292f1..45f48de70 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -27,7 +27,7 @@ def test_exec_dunder_file(self): p = subprocess.Popen( [sys.executable] + ["-m", "bpython.curtsies", f.name], stderr=subprocess.PIPE, - text=True, + universal_newlines=True, ) (_, stderr) = p.communicate() @@ -66,7 +66,7 @@ def test_exec_nonascii_file_linenums(self): p = subprocess.Popen( [sys.executable, "-m", "bpython.curtsies", f.name], stderr=subprocess.PIPE, - text=True, + universal_newlines=True, ) (_, stderr) = p.communicate() From b333f28111844c32e2774e77999ede737d9fe60d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 22:23:28 +0100 Subject: [PATCH 1191/1650] Run tests with tty (fixes #869) --- bpython/test/test_args.py | 81 +++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 45f48de70..11eaf7322 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -1,4 +1,8 @@ +import errno +import os +import pty import re +import select import subprocess import sys import tempfile @@ -6,9 +10,58 @@ from textwrap import dedent from bpython import args +from bpython.config import getpreferredencoding from bpython.test import FixLanguageTestCase as TestCase +def run_with_tty(command): + # based on https://stackoverflow.com/questions/52954248/capture-output-as-a-tty-in-python + master_stdout, slave_stdout = pty.openpty() + master_stderr, slave_stderr = pty.openpty() + master_stdin, slave_stdin = pty.openpty() + + p = subprocess.Popen( + command, + stdout=slave_stdout, + stderr=slave_stderr, + stdin=slave_stdin, + close_fds=True, + ) + for fd in (slave_stdout, slave_stderr, slave_stdin): + os.close(fd) + + readable = [master_stdout, master_stderr] + result = {master_stdout: b"", master_stderr: b""} + try: + while readable: + ready, _, _ = select.select(readable, [], [], 1) + for fd in ready: + try: + data = os.read(fd, 512) + except OSError as e: + if e.errno != errno.EIO: + raise + # EIO means EOF on some systems + readable.remove(fd) + else: + if not data: # EOF + readable.remove(fd) + result[fd] += data + finally: + for fd in (master_stdout, master_stderr, master_stdin): + os.close(fd) + if p.poll() is None: + p.kill() + p.wait() + if p.returncode: + raise RuntimeError(f"Subprocess exited with {p.returncode}") + + return ( + result[master_stdout].decode(getpreferredencoding()), + result[master_stderr].decode(getpreferredencoding()), + ) + + class TestExecArgs(unittest.TestCase): # These tests are currently not very useful. Under pytest neither stdout nor stdin are ttys, # hence bpython.args.parse will just exectute code by falling back to Python. @@ -24,13 +77,9 @@ def test_exec_dunder_file(self): ) ) f.flush() - p = subprocess.Popen( - [sys.executable] + ["-m", "bpython.curtsies", f.name], - stderr=subprocess.PIPE, - universal_newlines=True, + _, stderr = run_with_tty( + [sys.executable] + ["-m", "bpython.curtsies", f.name] ) - (_, stderr) = p.communicate() - self.assertEqual(stderr.strip(), f.name) def test_exec_nonascii_file(self): @@ -44,33 +93,25 @@ def test_exec_nonascii_file(self): ) ) f.flush() - try: - subprocess.check_call( - [sys.executable, "-m", "bpython.curtsies", f.name] - ) - except subprocess.CalledProcessError: - self.fail("Error running module with nonascii characters") + _, stderr = run_with_tty( + [sys.executable, "-m", "bpython.curtsies", f.name], + ) + self.assertEqual(len(stderr), 0) def test_exec_nonascii_file_linenums(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write( dedent( """\ - #!/usr/bin/env python - # coding: utf-8 1/0 """ ) ) f.flush() - p = subprocess.Popen( + _, stderr = run_with_tty( [sys.executable, "-m", "bpython.curtsies", f.name], - stderr=subprocess.PIPE, - universal_newlines=True, ) - (_, stderr) = p.communicate() - - self.assertIn("line 3", clean_colors(stderr)) + self.assertIn("line 1", clean_colors(stderr)) def clean_colors(s): From f0048eb71142deea2d5ef8bdbb412f9fe8ab0e1f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 22:49:05 +0100 Subject: [PATCH 1192/1650] Only check exit code if no other exception was raised --- bpython/test/test_args.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index 11eaf7322..febf28eab 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -53,8 +53,9 @@ def run_with_tty(command): if p.poll() is None: p.kill() p.wait() - if p.returncode: - raise RuntimeError(f"Subprocess exited with {p.returncode}") + + if p.returncode: + raise RuntimeError(f"Subprocess exited with {p.returncode}") return ( result[master_stdout].decode(getpreferredencoding()), From 26fc7cf03a39a34abe4c2d58ddcaf161633d96f5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 24 Jan 2021 22:50:27 +0100 Subject: [PATCH 1193/1650] Remove comment --- bpython/test/test_args.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bpython/test/test_args.py b/bpython/test/test_args.py index febf28eab..36f233ebc 100644 --- a/bpython/test/test_args.py +++ b/bpython/test/test_args.py @@ -64,9 +64,6 @@ def run_with_tty(command): class TestExecArgs(unittest.TestCase): - # These tests are currently not very useful. Under pytest neither stdout nor stdin are ttys, - # hence bpython.args.parse will just exectute code by falling back to Python. - def test_exec_dunder_file(self): with tempfile.NamedTemporaryFile(mode="w") as f: f.write( From bdbd814f16fab7774b72cfb57e233bff6f7a0026 Mon Sep 17 00:00:00 2001 From: supakeen Date: Mon, 25 Jan 2021 09:14:16 +0100 Subject: [PATCH 1194/1650] Minor changelog typo. --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4a0448347..4d4d2d396 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,7 +17,7 @@ New features: Fixes: -* #847: Fix import completion of modles +* #847: Fix import completion of modules * #857: Replace remaining use of deprecated imp with importlib * #862: Upgrade curtsies version requirements Thanks to Kelsey Blair From f8a628c222a892799fe9b35245d038a712303740 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 25 Jan 2021 11:13:09 +0100 Subject: [PATCH 1195/1650] Start development of 0.22 --- CHANGELOG.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4d4d2d396..e4d2c6be8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,17 @@ Changelog ========= +0.22 +---- + +General information: + +New features: + +Fixes: + +Changes to dependencies: + 0.21 ---- From 5c45d9463f5fe49f9acb837a5ff304fac0952463 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 27 Jan 2021 23:26:11 +0100 Subject: [PATCH 1196/1650] Fix inclusion of themes in sdist --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 8694b9e27..a569201d3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,9 +8,9 @@ include data/org.bpython-interpreter.bpython.appdata.xml include doc/sphinx/source/conf.py include doc/sphinx/source/*.rst include doc/sphinx/source/logo.png -include *.theme include bpython/test/*.py include bpython/test/*.theme include bpython/translations/*/LC_MESSAGES/bpython.po include bpython/translations/*/LC_MESSAGES/bpython.mo include bpython/sample-config +include theme/*.theme From 49aab4118e14b8cba6231ea48d95bd657703c6f3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 28 Jan 2021 00:07:28 +0100 Subject: [PATCH 1197/1650] Also make sure that simplerepl.py is contained in sdist --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index a569201d3..eb5d7f2fb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,7 +5,7 @@ include LICENSE include data/bpython.png include data/org.bpython-interpreter.bpython.desktop include data/org.bpython-interpreter.bpython.appdata.xml -include doc/sphinx/source/conf.py +include doc/sphinx/source/*.py include doc/sphinx/source/*.rst include doc/sphinx/source/logo.png include bpython/test/*.py From 0a83c13661bba8b5f16e17a8e20aab133f47c27b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 29 Jan 2021 14:28:00 +0100 Subject: [PATCH 1198/1650] Import setuptools before distutils --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f73ecf904..0aac54bdc 100755 --- a/setup.py +++ b/setup.py @@ -5,8 +5,8 @@ import re import subprocess -from distutils.command.build import build from setuptools import setup +from distutils.command.build import build try: from babel.messages import frontend as babel From 36d471c12d04e8b9731e41ecd8f40f0e34a4e24d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 29 Jan 2021 14:38:31 +0100 Subject: [PATCH 1199/1650] Fetch all tags to avoid 'unknown' version warning during build --- .github/workflows/build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ec62bca26..9af5bb048 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -16,6 +16,8 @@ jobs: python-version: [3.6, 3.7, 3.8, 3.9, pypy3] steps: - uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: From 2c5c3498fbab231f6acf2691b2f5d8814d44536a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 29 Jan 2021 22:05:39 +0100 Subject: [PATCH 1200/1650] Call functools.wraps on method --- bpython/lazyre.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 01747f819..67a9de3dd 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -20,6 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +import functools import re @@ -35,6 +36,7 @@ def __init__(self, regex, flags=0): self.compiled = None def compile_regex(method): + @functools.wraps(method) def _impl(self, *args, **kwargs): if self.compiled is None: self.compiled = re.compile(self.regex, self.flags) From 40460d1a8d5de1b0f697533e0fbc1da7d9e7cb4d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 29 Jan 2021 22:21:47 +0100 Subject: [PATCH 1201/1650] Use f-string --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 94841e044..5eae9f7ac 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -511,7 +511,7 @@ def matches(self, cursor_offset, line, **kwargs): return None if argspec: matches = { - name + "=" + f"{name}=" for name in argspec[1][0] if isinstance(name, str) and name.startswith(r.word) } From a837cb25db4a57c9638b4ba8dd33728953f68cfd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 29 Jan 2021 22:21:56 +0100 Subject: [PATCH 1202/1650] Avoid a list comprehension --- bpython/autocomplete.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 5eae9f7ac..89b4abf94 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -544,8 +544,8 @@ def matches(self, cursor_offset, line, **kwargs): except EvaluationError: return set() - # strips leading dot - matches = [m[1:] for m in self.attr_lookup(obj, "", attr.word)] + # strips leading dot + matches = (m[1:] for m in self.attr_lookup(obj, "", attr.word)) return {m for m in matches if few_enough_underscores(attr.word, m)} From 23874727accfeba1f0f4e0ec23b983eace72a57e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 29 Jan 2021 22:59:24 +0100 Subject: [PATCH 1203/1650] Fix typo --- bpython/args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/args.py b/bpython/args.py index e877ac1c1..3d5396625 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -128,7 +128,7 @@ def callback(group): "args", nargs=argparse.REMAINDER, help=_( - "File to extecute and additional arguments passed on to the executed script." + "File to execute and additional arguments passed on to the executed script." ), ) From e638475ccca615d83c0946ed48023c1a0fc8e612 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 29 Jan 2021 23:01:43 +0100 Subject: [PATCH 1204/1650] Update translations --- bpython/translations/bpython.pot | 98 ++++++++-------- .../translations/de/LC_MESSAGES/bpython.po | 106 +++++++++--------- .../translations/es_ES/LC_MESSAGES/bpython.po | 96 ++++++++-------- .../translations/fr_FR/LC_MESSAGES/bpython.po | 96 ++++++++-------- .../translations/it_IT/LC_MESSAGES/bpython.po | 96 ++++++++-------- .../translations/nl_NL/LC_MESSAGES/bpython.po | 96 ++++++++-------- 6 files changed, 288 insertions(+), 300 deletions(-) diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index df60dee43..7b23845d7 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.20.1.post128\n" +"Project-Id-Version: bpython 0.22.dev8\n" "Report-Msgid-Bugs-To: https://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-01 15:21+0100\n" +"POT-Creation-Date: 2021-01-29 22:59+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:92 +#: bpython/args.py:91 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -25,26 +25,24 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:102 +#: bpython/args.py:101 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:108 +#: bpython/args.py:107 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:114 +#: bpython/args.py:113 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:120 +#: bpython/args.py:119 msgid "Print version and exit." msgstr "" -#: bpython/args.py:131 -msgid "" -"File to extecute and additional arguments passed on to the executed " -"script." +#: bpython/args.py:130 +msgid "File to execute and additional arguments passed on to the executed script." msgstr "" #: bpython/cli.py:312 bpython/urwid.py:537 @@ -124,125 +122,125 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:824 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:840 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:848 +#: bpython/repl.py:844 msgid "overwrite" msgstr "" -#: bpython/repl.py:850 +#: bpython/repl.py:846 msgid "append" msgstr "" -#: bpython/repl.py:862 bpython/repl.py:1169 +#: bpython/repl.py:858 bpython/repl.py:1165 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:864 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:870 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:877 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:879 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:888 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:890 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:898 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:908 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:917 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:922 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:960 +#: bpython/repl.py:956 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:964 bpython/repl.py:968 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:975 +#: bpython/repl.py:971 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1147 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1179 +#: bpython/repl.py:1175 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1185 +#: bpython/repl.py:1181 #, python-format msgid "Error editing config file: %s" msgstr "" @@ -281,55 +279,55 @@ msgid "" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:312 +#: bpython/curtsiesfrontend/repl.py:324 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:314 +#: bpython/curtsiesfrontend/repl.py:326 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:663 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:661 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:970 +#: bpython/curtsiesfrontend/repl.py:990 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:985 +#: bpython/curtsiesfrontend/repl.py:1005 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:995 +#: bpython/curtsiesfrontend/repl.py:1015 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1026 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1012 +#: bpython/curtsiesfrontend/repl.py:1032 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1017 +#: bpython/curtsiesfrontend/repl.py:1037 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1958 +#: bpython/curtsiesfrontend/repl.py:1985 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index e903a3ee7..70684700b 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-01 15:21+0100\n" -"PO-Revision-Date: 2020-10-29 12:26+0100\n" +"POT-Creation-Date: 2021-01-29 22:59+0100\n" +"PO-Revision-Date: 2021-01-29 22:58+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: de\n" "Language-Team: de \n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:92 +#: bpython/args.py:91 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -29,26 +29,24 @@ msgstr "" "Hinweis: Wenn bpython Argumente übergeben bekommt, die nicht verstanden " "werden, wird der normale Python Interpreter ausgeführt." -#: bpython/args.py:102 +#: bpython/args.py:101 msgid "Use CONFIG instead of default config file." msgstr "Verwende CONFIG antatt der standardmäßigen Konfigurationsdatei." -#: bpython/args.py:108 +#: bpython/args.py:107 msgid "Drop to bpython shell after running file instead of exiting." msgstr "Verbleibe in bpython nach dem Ausführen der Datei." -#: bpython/args.py:114 +#: bpython/args.py:113 msgid "Don't flush the output to stdout." msgstr "Gib Ausgabe beim Beenden nicht ernaut auf stdout aus." -#: bpython/args.py:120 +#: bpython/args.py:119 msgid "Print version and exit." msgstr "Zeige Versionsinformationen an und beende." -#: bpython/args.py:131 -msgid "" -"File to extecute and additional arguments passed on to the executed " -"script." +#: bpython/args.py:130 +msgid "File to execute and additional arguments passed on to the executed script." msgstr "" #: bpython/cli.py:312 bpython/urwid.py:537 @@ -69,7 +67,7 @@ msgstr "Speichern" #: bpython/cli.py:1694 msgid "Pastebin" -msgstr "" +msgstr "Pastebin" #: bpython/cli.py:1695 msgid "Pager" @@ -99,7 +97,7 @@ msgstr "" #: bpython/curtsies.py:148 msgid "curtsies arguments" -msgstr "" +msgstr "Argumente für curtsies" #: bpython/curtsies.py:149 msgid "Additional arguments specific to the curtsies-based REPL." @@ -131,131 +129,131 @@ msgstr "Keine Ausgabe von Hilfsprogramm vorhanden." msgid "Failed to recognize the helper program's output as an URL." msgstr "Konnte Ausgabe von Hilfsprogramm nicht verarbeiten." -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "Nichts um Quellcode abzurufen" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" -#: bpython/repl.py:824 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "In Datei speichern (Esc um abzubrechen): " -#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "Speichern abgebrochen." -#: bpython/repl.py:840 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen? " -#: bpython/repl.py:848 +#: bpython/repl.py:844 msgid "overwrite" msgstr "überschreiben" -#: bpython/repl.py:850 +#: bpython/repl.py:846 msgid "append" msgstr "anhängen" -#: bpython/repl.py:862 bpython/repl.py:1169 +#: bpython/repl.py:858 bpython/repl.py:1165 #, python-format msgid "Error writing file '%s': %s" msgstr "Fehler beim Schreiben in Datei '%s': %s" -#: bpython/repl.py:864 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "Nach %s gespeichert." -#: bpython/repl.py:870 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/repl.py:877 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/repl.py:879 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:888 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:890 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:898 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch..." -#: bpython/repl.py:908 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:917 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" -msgstr "" +msgstr "Pastebin URL: %s - URL zum Löschen: %s" -#: bpython/repl.py:922 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" -msgstr "" +msgstr "Pastebin URL: %s" -#: bpython/repl.py:960 +#: bpython/repl.py:956 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" "Wie viele Zeilen rückgängig machen? (Rückgängigmachen wird bis zu ~%.1f " "Sekunden brauchen) [1]" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:964 bpython/repl.py:968 msgid "Undo canceled" msgstr "Rückgängigmachen abgebrochen" -#: bpython/repl.py:975 +#: bpython/repl.py:971 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "Mache %d Zeile rückgängig... (ungefähr %.1f Sekunden)" msgstr[1] "Mache %d Zeilen rückgängig... (ungefähr %.1f Sekunden)" -#: bpython/repl.py:1151 +#: bpython/repl.py:1147 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" "Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " "werden? (j/N)" -#: bpython/repl.py:1179 +#: bpython/repl.py:1175 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" "bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " "Änderungen übernommen werden." -#: bpython/repl.py:1185 +#: bpython/repl.py:1181 #, python-format msgid "Error editing config file: %s" msgstr "Fehler beim Bearbeiten der Konfigurationsdatei: %s" @@ -301,57 +299,57 @@ msgstr "" "`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " "unterstützt und wird in einer zukünftigen Version entfernt werden." -#: bpython/curtsiesfrontend/repl.py:312 +#: bpython/curtsiesfrontend/repl.py:324 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" -#: bpython/curtsiesfrontend/repl.py:314 +#: bpython/curtsiesfrontend/repl.py:326 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:663 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" -#: bpython/curtsiesfrontend/repl.py:661 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Reloaded at %s because %s modified." msgstr "Bei %s neugeladen, da %s modifiziert wurde." -#: bpython/curtsiesfrontend/repl.py:970 +#: bpython/curtsiesfrontend/repl.py:990 msgid "Session not reevaluated because it was not edited" msgstr "Die Sitzung wurde nicht neu ausgeführt, da sie nicht berabeitet wurde" -#: bpython/curtsiesfrontend/repl.py:985 +#: bpython/curtsiesfrontend/repl.py:1005 msgid "Session not reevaluated because saved file was blank" msgstr "Die Sitzung wurde nicht neu ausgeführt, da die gespeicherte Datei leer war" -#: bpython/curtsiesfrontend/repl.py:995 +#: bpython/curtsiesfrontend/repl.py:1015 msgid "Session edited and reevaluated" msgstr "Sitzung bearbeitet und neu ausgeführt" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1026 #, python-format msgid "Reloaded at %s by user." msgstr "Bei %s vom Benutzer neu geladen." -#: bpython/curtsiesfrontend/repl.py:1012 +#: bpython/curtsiesfrontend/repl.py:1032 msgid "Auto-reloading deactivated." msgstr "Automatisches Neuladen deaktiviert." -#: bpython/curtsiesfrontend/repl.py:1017 +#: bpython/curtsiesfrontend/repl.py:1037 msgid "Auto-reloading active, watching for file changes..." msgstr "Automatisches Neuladen ist aktiv; beobachte Dateiänderungen..." -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading not available because watchdog not installed." msgstr "" "Automatisches Neuladen ist nicht verfügbar da watchdog nicht installiert " "ist." -#: bpython/curtsiesfrontend/repl.py:1958 +#: bpython/curtsiesfrontend/repl.py:1985 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 526fb57ce..3d5704ae7 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-01 15:21+0100\n" +"POT-Creation-Date: 2021-01-29 22:59+0100\n" "PO-Revision-Date: 2020-10-29 12:22+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: es_ES\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:92 +#: bpython/args.py:91 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -26,26 +26,24 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:102 +#: bpython/args.py:101 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:108 +#: bpython/args.py:107 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:114 +#: bpython/args.py:113 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:120 +#: bpython/args.py:119 msgid "Print version and exit." msgstr "" -#: bpython/args.py:131 -msgid "" -"File to extecute and additional arguments passed on to the executed " -"script." +#: bpython/args.py:130 +msgid "File to execute and additional arguments passed on to the executed script." msgstr "" #: bpython/cli.py:312 bpython/urwid.py:537 @@ -125,125 +123,125 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:824 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:840 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:848 +#: bpython/repl.py:844 msgid "overwrite" msgstr "" -#: bpython/repl.py:850 +#: bpython/repl.py:846 msgid "append" msgstr "" -#: bpython/repl.py:862 bpython/repl.py:1169 +#: bpython/repl.py:858 bpython/repl.py:1165 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:864 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:870 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:877 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:879 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:888 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:890 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:898 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:908 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:917 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:922 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:960 +#: bpython/repl.py:956 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:964 bpython/repl.py:968 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:975 +#: bpython/repl.py:971 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1147 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1179 +#: bpython/repl.py:1175 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1185 +#: bpython/repl.py:1181 #, python-format msgid "Error editing config file: %s" msgstr "" @@ -284,55 +282,55 @@ msgid "" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:312 +#: bpython/curtsiesfrontend/repl.py:324 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:314 +#: bpython/curtsiesfrontend/repl.py:326 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:663 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:661 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:970 +#: bpython/curtsiesfrontend/repl.py:990 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:985 +#: bpython/curtsiesfrontend/repl.py:1005 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:995 +#: bpython/curtsiesfrontend/repl.py:1015 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1026 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1012 +#: bpython/curtsiesfrontend/repl.py:1032 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1017 +#: bpython/curtsiesfrontend/repl.py:1037 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1958 +#: bpython/curtsiesfrontend/repl.py:1985 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index b62785548..b2b4888d8 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-01 15:21+0100\n" +"POT-Creation-Date: 2021-01-29 22:59+0100\n" "PO-Revision-Date: 2020-10-29 12:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: fr_FR\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:92 +#: bpython/args.py:91 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -28,28 +28,26 @@ msgstr "" "NOTE: Si bpython ne reconnaît pas un des arguments fournis, " "l'interpréteur Python classique sera lancé" -#: bpython/args.py:102 +#: bpython/args.py:101 msgid "Use CONFIG instead of default config file." msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." -#: bpython/args.py:108 +#: bpython/args.py:107 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" "Aller dans le shell bpython après l'exécution du fichier au lieu de " "quitter." -#: bpython/args.py:114 +#: bpython/args.py:113 msgid "Don't flush the output to stdout." msgstr "Ne pas purger la sortie vers stdout." -#: bpython/args.py:120 +#: bpython/args.py:119 msgid "Print version and exit." msgstr "Afficher la version et quitter." -#: bpython/args.py:131 -msgid "" -"File to extecute and additional arguments passed on to the executed " -"script." +#: bpython/args.py:130 +msgid "File to execute and additional arguments passed on to the executed script." msgstr "" #: bpython/cli.py:312 bpython/urwid.py:537 @@ -129,125 +127,125 @@ msgstr "pas de sortie du programme externe." msgid "Failed to recognize the helper program's output as an URL." msgstr "la sortie du programme externe ne correspond pas à une URL." -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "Impossible de récupérer le source: %s" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "Impossible d'accéder au source de %r" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "Pas de code source trouvé pour %s" -#: bpython/repl.py:824 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:840 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:848 +#: bpython/repl.py:844 msgid "overwrite" msgstr "" -#: bpython/repl.py:850 +#: bpython/repl.py:846 msgid "append" msgstr "" -#: bpython/repl.py:862 bpython/repl.py:1169 +#: bpython/repl.py:858 bpython/repl.py:1165 #, python-format msgid "Error writing file '%s': %s" msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" -#: bpython/repl.py:864 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:870 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "Pas de presse-papier disponible." -#: bpython/repl.py:877 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "Impossible de copier vers le presse-papier." -#: bpython/repl.py:879 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "Contenu copié vers le presse-papier." -#: bpython/repl.py:888 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "Tampon Pastebin ? (o/N) " -#: bpython/repl.py:890 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "Pastebin abandonné." -#: bpython/repl.py:898 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" -#: bpython/repl.py:904 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "Envoi des donnés à pastebin..." -#: bpython/repl.py:908 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "Echec du téléchargement: %s" -#: bpython/repl.py:917 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "URL Pastebin: %s - URL de suppression: %s" -#: bpython/repl.py:922 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" msgstr "URL Pastebin: %s" -#: bpython/repl.py:960 +#: bpython/repl.py:956 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:964 bpython/repl.py:968 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:975 +#: bpython/repl.py:971 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1147 msgid "Config file does not exist - create new from default? (y/N)" msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" -#: bpython/repl.py:1179 +#: bpython/repl.py:1175 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1185 +#: bpython/repl.py:1181 #, python-format msgid "Error editing config file: %s" msgstr "" @@ -290,55 +288,55 @@ msgid "" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:312 +#: bpython/curtsiesfrontend/repl.py:324 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" -#: bpython/curtsiesfrontend/repl.py:314 +#: bpython/curtsiesfrontend/repl.py:326 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:663 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" -#: bpython/curtsiesfrontend/repl.py:661 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:970 +#: bpython/curtsiesfrontend/repl.py:990 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:985 +#: bpython/curtsiesfrontend/repl.py:1005 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:995 +#: bpython/curtsiesfrontend/repl.py:1015 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1026 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1012 +#: bpython/curtsiesfrontend/repl.py:1032 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1017 +#: bpython/curtsiesfrontend/repl.py:1037 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1958 +#: bpython/curtsiesfrontend/repl.py:1985 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index 46ae2a155..c4e76797b 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-01 15:21+0100\n" +"POT-Creation-Date: 2021-01-29 22:59+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: it_IT\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:92 +#: bpython/args.py:91 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -26,26 +26,24 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:102 +#: bpython/args.py:101 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:108 +#: bpython/args.py:107 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:114 +#: bpython/args.py:113 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:120 +#: bpython/args.py:119 msgid "Print version and exit." msgstr "" -#: bpython/args.py:131 -msgid "" -"File to extecute and additional arguments passed on to the executed " -"script." +#: bpython/args.py:130 +msgid "File to execute and additional arguments passed on to the executed script." msgstr "" #: bpython/cli.py:312 bpython/urwid.py:537 @@ -125,125 +123,125 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:824 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:840 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:848 +#: bpython/repl.py:844 msgid "overwrite" msgstr "" -#: bpython/repl.py:850 +#: bpython/repl.py:846 msgid "append" msgstr "" -#: bpython/repl.py:862 bpython/repl.py:1169 +#: bpython/repl.py:858 bpython/repl.py:1165 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:864 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:870 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:877 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:879 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:888 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:890 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:898 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:908 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:917 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:922 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:960 +#: bpython/repl.py:956 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:964 bpython/repl.py:968 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:975 +#: bpython/repl.py:971 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1147 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1179 +#: bpython/repl.py:1175 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1185 +#: bpython/repl.py:1181 #, python-format msgid "Error editing config file: %s" msgstr "" @@ -282,55 +280,55 @@ msgid "" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:312 +#: bpython/curtsiesfrontend/repl.py:324 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:314 +#: bpython/curtsiesfrontend/repl.py:326 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:663 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:661 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:970 +#: bpython/curtsiesfrontend/repl.py:990 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:985 +#: bpython/curtsiesfrontend/repl.py:1005 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:995 +#: bpython/curtsiesfrontend/repl.py:1015 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1026 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1012 +#: bpython/curtsiesfrontend/repl.py:1032 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1017 +#: bpython/curtsiesfrontend/repl.py:1037 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1958 +#: bpython/curtsiesfrontend/repl.py:1985 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index be858262f..87c957390 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-01 15:21+0100\n" +"POT-Creation-Date: 2021-01-29 22:59+0100\n" "PO-Revision-Date: 2020-10-29 12:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: nl_NL\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:92 +#: bpython/args.py:91 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -26,26 +26,24 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:102 +#: bpython/args.py:101 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:108 +#: bpython/args.py:107 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:114 +#: bpython/args.py:113 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:120 +#: bpython/args.py:119 msgid "Print version and exit." msgstr "" -#: bpython/args.py:131 -msgid "" -"File to extecute and additional arguments passed on to the executed " -"script." +#: bpython/args.py:130 +msgid "File to execute and additional arguments passed on to the executed script." msgstr "" #: bpython/cli.py:312 bpython/urwid.py:537 @@ -125,125 +123,125 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:657 +#: bpython/repl.py:653 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:662 +#: bpython/repl.py:658 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:667 +#: bpython/repl.py:663 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:669 +#: bpython/repl.py:665 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:824 +#: bpython/repl.py:820 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:826 bpython/repl.py:829 bpython/repl.py:853 +#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:840 +#: bpython/repl.py:836 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:848 +#: bpython/repl.py:844 msgid "overwrite" msgstr "" -#: bpython/repl.py:850 +#: bpython/repl.py:846 msgid "append" msgstr "" -#: bpython/repl.py:862 bpython/repl.py:1169 +#: bpython/repl.py:858 bpython/repl.py:1165 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:864 +#: bpython/repl.py:860 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:870 +#: bpython/repl.py:866 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:877 +#: bpython/repl.py:873 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:879 +#: bpython/repl.py:875 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:888 +#: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:890 +#: bpython/repl.py:886 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:898 +#: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:900 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:908 +#: bpython/repl.py:904 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:917 +#: bpython/repl.py:913 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:922 +#: bpython/repl.py:918 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:960 +#: bpython/repl.py:956 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:968 bpython/repl.py:972 +#: bpython/repl.py:964 bpython/repl.py:968 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:975 +#: bpython/repl.py:971 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1151 +#: bpython/repl.py:1147 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1179 +#: bpython/repl.py:1175 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1185 +#: bpython/repl.py:1181 #, python-format msgid "Error editing config file: %s" msgstr "" @@ -282,55 +280,55 @@ msgid "" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:312 +#: bpython/curtsiesfrontend/repl.py:324 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:314 +#: bpython/curtsiesfrontend/repl.py:326 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:643 +#: bpython/curtsiesfrontend/repl.py:663 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:661 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:970 +#: bpython/curtsiesfrontend/repl.py:990 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:985 +#: bpython/curtsiesfrontend/repl.py:1005 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:995 +#: bpython/curtsiesfrontend/repl.py:1015 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1026 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1012 +#: bpython/curtsiesfrontend/repl.py:1032 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1017 +#: bpython/curtsiesfrontend/repl.py:1037 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1023 +#: bpython/curtsiesfrontend/repl.py:1043 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1958 +#: bpython/curtsiesfrontend/repl.py:1985 msgid "" "\n" "Thanks for using bpython!\n" From f924f2c7867ed0816b0314d4702a7805ad6014ca Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 30 Jan 2021 09:47:29 +0100 Subject: [PATCH 1205/1650] Check colors in custom theme config and error out on unknown codes --- bpython/config.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bpython/config.py b/bpython/config.py index 5af10d1dd..cdd8ca35d 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -31,10 +31,17 @@ from xdg import BaseDirectory from .autocomplete import AutocompleteModes +from .curtsiesfrontend.parse import CNAMES default_completion = AutocompleteModes.SIMPLE +class UnknownColorCode(Exception): + def __init__(self, key, color): + self.key = key + self.color = color + + class Struct: """Simple class for instantiating objects we can add arbitrary attributes to and use for various arbitrary things.""" @@ -324,6 +331,11 @@ def get_key_no_doublebind(command): f"Could not load theme '{color_scheme_name}' from {path}.\n" ) sys.exit(1) + except UnknownColorCode as ucc: + sys.stderr.write( + f"Theme '{color_scheme_name}' contains invalid color: {ucc.key} = {ucc.color}.\n" + ) + sys.exit(1) # expand path of history file struct.hist_file = Path(struct.hist_file).expanduser() @@ -362,6 +374,8 @@ def load_theme(struct, path, colors, default_colors): colors[k] = theme.get("syntax", k) else: colors[k] = theme.get("interface", k) + if colors[k].lower() not in CNAMES: + raise UnknownColorCode(k, colors[k]) # Check against default theme to see if all values are defined for k, v in default_colors.items(): From 8d217395e900f75a404cb632cb3dec7a7b02e121 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 30 Jan 2021 09:49:34 +0100 Subject: [PATCH 1206/1650] Remove a branch --- bpython/autocomplete.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 89b4abf94..15eda6a9c 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -175,10 +175,7 @@ def few_enough_underscores(current, match): return True elif current.startswith("_") and not match.startswith("__"): return True - elif match.startswith("_"): - return False - else: - return True + return not match.startswith("_") def method_match_simple(word, size, text): From 3d315dcd6a4e65963184ddbb933a0bab2a2e5d8c Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 30 Jan 2021 07:59:51 -0800 Subject: [PATCH 1207/1650] Enable crashers tests for Curtsies (#881) Enable crashers tests for Curtsies --- bpython/test/test_crashers.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index f9afe7947..64abff3e3 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -72,6 +72,10 @@ def next(self): self.data = self.data[index + 4 :] self.transport.write(input.encode(encoding)) self.state = next(self.states) + elif self.data == "\x1b[6n": + # this is a cursor position query + # respond that cursor is on row 2, column 1 + self.transport.write("\x1b[2;1R".encode(encoding)) else: self.transport.closeStdin() if self.transport.pid is not None: @@ -94,6 +98,7 @@ def processExited(self, reason): f"bpython.{self.backend}", "--config", str(TEST_CONFIG), + "-q", # prevents version greeting ), env={ "TERM": "vt100", @@ -128,6 +133,11 @@ def check_no_traceback(self, data): self.assertNotIn("Traceback", data) +@unittest.skipIf(reactor is None, "twisted is not available") +class CurtsiesCrashersTest(TrialTestCase, CrashersTest): + backend = "curtsies" + + @unittest.skipIf(reactor is None, "twisted is not available") class CursesCrashersTest(TrialTestCase, CrashersTest): backend = "cli" From 76aad2fa5ea34ace62271b2bcf52f5dd5b7da9ad Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 3 Feb 2021 09:41:35 +0100 Subject: [PATCH 1208/1650] Turn Appdata into metainfo --- ...pdata.xml => org.bpython-interpreter.bpython.metainfo.xml} | 0 setup.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename data/{org.bpython-interpreter.bpython.appdata.xml => org.bpython-interpreter.bpython.metainfo.xml} (100%) diff --git a/data/org.bpython-interpreter.bpython.appdata.xml b/data/org.bpython-interpreter.bpython.metainfo.xml similarity index 100% rename from data/org.bpython-interpreter.bpython.appdata.xml rename to data/org.bpython-interpreter.bpython.metainfo.xml diff --git a/setup.py b/setup.py index 0aac54bdc..994e50855 100755 --- a/setup.py +++ b/setup.py @@ -162,8 +162,8 @@ def git_describe_to_python_version(version): ), # AppData ( - os.path.join("share", "appinfo"), - ["data/org.bpython-interpreter.bpython.appdata.xml"], + os.path.join("share", "metainfo"), + ["data/org.bpython-interpreter.bpython.metainfo.xml"], ), # icon (os.path.join("share", "pixmaps"), ["data/bpython.png"]), From ee87010964ee00d8fc1ee9882f374abc9c1978ea Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 4 Feb 2021 22:04:23 +0100 Subject: [PATCH 1209/1650] Fix autocompletion mode lookup --- bpython/autocomplete.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 15eda6a9c..c700d97ce 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -48,8 +48,8 @@ class AutocompleteModes(Enum): @classmethod def from_string(cls, value): - if value in cls.__members__: - return cls.__members__[value] + if value.upper() in cls.__members__: + return cls.__members__[value.upper()] return None From 5fac24d31819091a2626d98dac408b30dc7feb67 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 4 Feb 2021 22:05:22 +0100 Subject: [PATCH 1210/1650] No longer override autocompletion_mode setting --- bpython/curtsiesfrontend/repl.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 86c2cb1d0..5637b9d57 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -327,8 +327,6 @@ def __init__( ) else: banner = None - # only one implemented currently - config.autocomplete_mode = autocomplete.AutocompleteModes.SIMPLE if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1: config.cli_suggestion_width = 1 From a625d69fb55d427595846e086596911428756b7d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 4 Feb 2021 22:10:27 +0100 Subject: [PATCH 1211/1650] Make it possible to disable auto completion (fixes #883) --- bpython/autocomplete.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index c700d97ce..6b5645157 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -42,6 +42,7 @@ # Autocomplete modes class AutocompleteModes(Enum): + NONE = "none" SIMPLE = "simple" SUBSTRING = "substring" FUZZY = "fuzzy" @@ -178,6 +179,10 @@ def few_enough_underscores(current, match): return not match.startswith("_") +def method_match_none(word, size, text): + return False + + def method_match_simple(word, size, text): return word[:size] == text @@ -192,6 +197,7 @@ def method_match_fuzzy(word, size, text): MODES_MAP = { + AutocompleteModes.NONE: method_match_none, AutocompleteModes.SIMPLE: method_match_simple, AutocompleteModes.SUBSTRING: method_match_substring, AutocompleteModes.FUZZY: method_match_fuzzy, @@ -669,7 +675,7 @@ def get_default_completer(mode=AutocompleteModes.SIMPLE, module_gatherer=None): ), AttrCompletion(mode=mode), ExpressionAttributeCompletion(mode=mode), - ) + ) if mode != AutocompleteModes.NONE else tuple() def _callable_postfix(value, word): From 3ae41ef04963eba575d62f7db7d87d6be4d39349 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 4 Feb 2021 22:15:01 +0100 Subject: [PATCH 1212/1650] Update autocomplete_mode documentation --- doc/sphinx/source/configuration-options.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 2a1bbbe6c..c5b3ccfb0 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -15,11 +15,10 @@ When this is off, you can hit tab to see the suggestions. autocomplete_mode ^^^^^^^^^^^^^^^^^ -There are three modes for autocomplete. simple, substring, and fuzzy. Simple -matches methods with a common prefix, substring matches methods with a common -subsequence, and fuzzy matches methods with common characters (default: simple). - -As of version 0.14 this option has no effect, but is reserved for later use. +There are four modes for autocomplete: ``none``, ``simple``, ``substring``, and +``fuzzy``. Simple matches methods with a common prefix, substring matches +methods with a common subsequence, and fuzzy matches methods with common +characters (default: simple). None disables autocompletion. .. versionadded:: 0.12 From e940a37ff677b7c137a6f3d1e89c37a2eb0d4f2e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 4 Feb 2021 22:17:47 +0100 Subject: [PATCH 1213/1650] Apply black --- bpython/autocomplete.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 6b5645157..477b9651c 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -664,18 +664,25 @@ def get_completer(completers, cursor_offset, line, **kwargs): def get_default_completer(mode=AutocompleteModes.SIMPLE, module_gatherer=None): return ( - DictKeyCompletion(mode=mode), - ImportCompletion(module_gatherer, mode=mode), - FilenameCompletion(mode=mode), - MagicMethodCompletion(mode=mode), - MultilineJediCompletion(mode=mode), - CumulativeCompleter( - (GlobalCompletion(mode=mode), ParameterNameCompletion(mode=mode)), - mode=mode, - ), - AttrCompletion(mode=mode), - ExpressionAttributeCompletion(mode=mode), - ) if mode != AutocompleteModes.NONE else tuple() + ( + DictKeyCompletion(mode=mode), + ImportCompletion(module_gatherer, mode=mode), + FilenameCompletion(mode=mode), + MagicMethodCompletion(mode=mode), + MultilineJediCompletion(mode=mode), + CumulativeCompleter( + ( + GlobalCompletion(mode=mode), + ParameterNameCompletion(mode=mode), + ), + mode=mode, + ), + AttrCompletion(mode=mode), + ExpressionAttributeCompletion(mode=mode), + ) + if mode != AutocompleteModes.NONE + else tuple() + ) def _callable_postfix(value, word): From b63051e1c7de400d05fb34f55d38eec86398116b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Feb 2021 20:40:04 +0100 Subject: [PATCH 1214/1650] Do not abort early if nothing to output (fixes #884) Otherwise no TypeError is raised on b"" which is used by click to check if an output stream is a binary stream. --- bpython/curtsiesfrontend/repl.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 5637b9d57..3abb689bb 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1240,8 +1240,6 @@ def send_to_stdouterr(self, output): Must be able to handle FmtStrs because interpreter pass in tracebacks already formatted.""" - if not output: - return lines = output.split("\n") logger.debug("display_lines: %r", self.display_lines) self.current_stdouterr_line += lines[0] From 10072cb7afdffc60296a854ad27bc62d9e663958 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Feb 2021 21:18:24 +0100 Subject: [PATCH 1215/1650] Use itertools.chain instead of list addition --- bpython/curtsiesfrontend/repl.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3abb689bb..9a7ee0e08 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -2,6 +2,7 @@ import contextlib import errno import greenlet +import itertools import logging import os import re @@ -1259,7 +1260,9 @@ def send_to_stdouterr(self, output): ) ) # These can be FmtStrs, but self.all_logical_lines only wants strings - for line in [self.current_stdouterr_line] + lines[1:-1]: + for line in itertools.chain( + (self.current_stdouterr_line,), lines[1:-1] + ): if isinstance(line, FmtStr): self.all_logical_lines.append((line.s, LineType.OUTPUT)) else: From 13dc7067910039b4c9fd534a4ad3535cb598db4f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Feb 2021 21:25:41 +0100 Subject: [PATCH 1216/1650] Use relative import --- bpython/curtsiesfrontend/preprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index 714d9538a..8bf2924d8 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -1,7 +1,7 @@ """Tools for preparing code to be run in the REPL (removing blank lines, etc)""" -from bpython.lazyre import LazyReCompile +from ..lazyre import LazyReCompile # TODO specifically catch IndentationErrors instead of any syntax errors From b2927110f705ccdc43ffca7c1e1be059cc60b600 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Feb 2021 21:33:14 +0100 Subject: [PATCH 1217/1650] Use iterators instead of allocating extra lists --- bpython/curtsiesfrontend/preprocess.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index 8bf2924d8..e0d15f4ec 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -2,10 +2,10 @@ etc)""" from ..lazyre import LazyReCompile +from itertools import tee, islice, chain # TODO specifically catch IndentationErrors instead of any syntax errors - indent_empty_lines_re = LazyReCompile(r"\s*") tabs_to_spaces_re = LazyReCompile(r"^\t+") @@ -21,7 +21,11 @@ def indent_empty_lines(s, compiler): lines.pop() result_lines = [] - for p_line, line, n_line in zip([""] + lines[:-1], lines, lines[1:] + [""]): + prevs, lines, nexts = tee(lines, 3) + prevs = chain(("",), prevs) + nexts = chain(islice(nexts, 1, None), ("",)) + + for p_line, line, n_line in zip(prevs, lines, nexts): if len(line) == 0: p_indent = indent_empty_lines_re.match(p_line).group() n_indent = indent_empty_lines_re.match(n_line).group() From ac60146d7cd23e30004d64c8163ee15862d24937 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 9 Feb 2021 00:24:47 +0100 Subject: [PATCH 1218/1650] Update years and authors --- doc/sphinx/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py index 99f63344e..2c5263d90 100644 --- a/doc/sphinx/source/conf.py +++ b/doc/sphinx/source/conf.py @@ -38,7 +38,7 @@ # General information about the project. project = u'bpython' -copyright = u'2008-2015 Bob Farrell, Andreas Stuehrk et al.' +copyright = u'2008-2021 Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al.' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 55dbd77d5aa2b38d2640d7088c9fce62a0a424f2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 9 Feb 2021 00:26:26 +0100 Subject: [PATCH 1219/1650] Fix indentation --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e4d2c6be8..28cd66af2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -130,6 +130,7 @@ Support for Python 3.3 has been dropped. ------ Fixes: + * Reverted #670 temporarily due to performance impact on large strings being output. From c115cb956f9cc393d979d7c025ee3d47a006ab6d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 9 Feb 2021 00:37:10 +0100 Subject: [PATCH 1220/1650] Update pastebin example --- doc/sphinx/source/configuration-options.rst | 46 +++++++++++---------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index c5b3ccfb0..6d347761d 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -103,38 +103,42 @@ following helper program can be used to create `gists #!/usr/bin/env python import sys - import urllib2 + import requests import json def do_gist_json(s): """ Use json to post to github. """ gist_public = False - gist_url = 'https://api.github.com/gists' - - data = {'description': None, - 'public': None, - 'files' : { - 'sample': { 'content': None } - }} - data['description'] = 'Gist from BPython' - data['public'] = gist_public - data['files']['sample']['content'] = s - - req = urllib2.Request(gist_url, json.dumps(data), {'Content-Type': 'application/json'}) - try: - res = urllib2.urlopen(req) - except HTTPError, e: - return e + gist_url = "https://api.github.com/gists" + + data = { + "description": "Gist from bpython", + "public": gist_public, + "files": { + "sample": { + "content": s + }, + }, + } + + headers = { + "Content-Type": "application/json", + "X-Github-Username": "YOUR_USERNAME", + "Authorization": "token YOUR_TOKEN", + } try: + res = requests.post(gist_url, data=json.dumps(payload), headers=headers) + res.raise_for_status() json_res = json.loads(res.read()) - return json_res['html_url'] - except HTTPError, e: - return e + return json_res["html_url"] + except requests.exceptions.HTTPError as err: + return err + if __name__ == "__main__": s = sys.stdin.read() - print do_gist_json(s) + print(do_gist_json(s)) .. versionadded:: 0.12 From f3cc2204b87d2bf890e85af04eb2a774277bdf7a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 13 Feb 2021 15:45:13 +0100 Subject: [PATCH 1221/1650] Use declarative build config as much as possible --- pyproject.toml | 6 ++++++ setup.cfg | 40 +++++++++++++++++++++++++++++++++++++++- setup.py | 49 ------------------------------------------------- 3 files changed, 45 insertions(+), 50 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2ea7bf3e9..79a558e88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,9 @@ +[build-system] +requires = [ + "setuptools >= 43", + "wheel", +] + [tool.black] line-length = 80 target_version = ["py36"] diff --git a/setup.cfg b/setup.cfg index bfed3932c..7dfb1d58c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,43 @@ [metadata] -license_files = LICENSE +name = bpython +long_description = file: README.rst +license = MIT +license_file = LICENSE +url = https://www.bpython-interpreter.org/ +project_urls = + GitHub = https://github.com/bpython/bpython + Documentation = https://doc.bpython-interpreter.org +classifiers = + Programming Language :: Python :: 3 + +[options] +python_requires = >=3.6 +packages = + bpython + bpython.curtsiesfrontend + bpython.test + bpython.test.fodder + bpython.translations + bpdb +install_requires = + pygments + requests + curtsies >=0.3.5 + greenlet + cwcwidth + pyxdg + +[options.extras_require] +urwid = urwid +watch = watchdog +jedi = jedi >= 0.16 + +[options.entry_points] +console_scripts = + bpython = bpython.curtsies:main + bpython-curses = bpython.cli:main + bpython-urwid = bpython.urwid:main [urwid] + bpdb = bpdb:main [init_catalog] domain = bpython diff --git a/setup.py b/setup.py index 994e50855..42dd2a309 100755 --- a/setup.py +++ b/setup.py @@ -170,43 +170,6 @@ def git_describe_to_python_version(version): ] data_files.extend(man_pages) -classifiers = [ - "Programming Language :: Python :: 3", -] - -install_requires = [ - "pygments", - "requests", - "curtsies >=0.3.5", - "greenlet", - "cwcwidth", - "pyxdg", -] - -extras_require = { - "urwid": ["urwid"], - "watch": ["watchdog"], - "jedi": ["jedi >=0.16"], -} - -packages = [ - "bpython", - "bpython.curtsiesfrontend", - "bpython.test", - "bpython.test.fodder", - "bpython.translations", - "bpdb", -] - -entry_points = { - "console_scripts": [ - "bpython = bpython.curtsies:main", - "bpython-curses = bpython.cli:main", - "bpython-urwid = bpython.urwid:main [urwid]", - "bpdb = bpdb:main", - ] -} - # translations mo_files = [] for language in os.listdir(translations_dir): @@ -215,27 +178,15 @@ def git_describe_to_python_version(version): mo_files.append(mo_subpath) setup( - name="bpython", version=version, author=__author__, author_email="robertanthonyfarrell@gmail.com", - description="Fancy Interface to the Python Interpreter", - license="MIT/X", - url="https://www.bpython-interpreter.org/", - long_description="""bpython is a fancy interface to the Python - interpreter for Unix-like operating systems.""", - classifiers=classifiers, - python_requires=">=3.6", - install_requires=install_requires, - extras_require=extras_require, - packages=packages, data_files=data_files, package_data={ "bpython": ["sample-config"], "bpython.translations": mo_files, "bpython.test": ["test.config", "test.theme"], }, - entry_points=entry_points, cmdclass=cmdclass, test_suite="bpython.test", ) From 8badb8e596fcab7362b906caacd067660ba11ec5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 14 Feb 2021 17:22:09 +0100 Subject: [PATCH 1222/1650] Improve logging configuration via command line options --- bpython/args.py | 34 ++++++++++++++++++++++++++++++- bpython/curtsies.py | 18 ---------------- doc/sphinx/source/man-bpython.rst | 4 ++-- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 3d5396625..9618cc0c2 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -25,10 +25,11 @@ Module to handle command line argument parsing, for all front-ends. """ +import argparse import importlib.util +import logging import os import sys -import argparse from . import __version__, __copyright__ from .config import default_config_path, loadini, Struct @@ -118,6 +119,18 @@ def callback(group): action="store_true", help=_("Print version and exit."), ) + parser.add_argument( + "--log-level", + "-l", + choices=("debug", "info", "warning", "error", "critical"), + default="error", + help=_("Set log level for logging"), + ) + parser.add_argument( + "--log-output", + "-L", + help=_("Log output file"), + ) if extras is not None: extras_group = parser.add_argument_group(extras[0], extras[1]) @@ -147,6 +160,25 @@ def callback(group): # Just let Python handle this os.execv(sys.executable, [sys.executable] + args) + # Configure logging handler + bpython_logger = logging.getLogger("bpython") + curtsies_logger = logging.getLogger("curtsies") + bpython_logger.setLevel(options.log_level.upper()) + curtsies_logger.setLevel(options.log_level.upper()) + if options.log_output: + handler = logging.FileHandler(filename=options.log_output) + handler.setFormatter( + logging.Formatter( + "%(asctime)s: %(name)s: %(levelname)s: %(message)s" + ) + ) + bpython_logger.addHandler(handler) + curtsies_logger.addHandler(handler) + bpython_logger.propagate = curtsies_logger.propagate = False + else: + bpython_logger.addHandler(logging.NullHandler()) + curtsies_logger.addHandler(logging.NullHandler()) + config = Struct() loadini(config, options.config) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index f9950997e..69d3f175f 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -129,12 +129,6 @@ def main(args=None, locals_=None, banner=None, welcome_message=None): translations.init() def curtsies_arguments(parser): - parser.add_argument( - "--log", - "-L", - action="count", - help=_("log debug messages to bpython.log"), - ) parser.add_argument( "--paste", "-p", @@ -150,18 +144,6 @@ def curtsies_arguments(parser): curtsies_arguments, ), ) - if options.log is None: - options.log = 0 - logging_levels = (logging.ERROR, logging.INFO, logging.DEBUG) - level = logging_levels[min(len(logging_levels) - 1, options.log)] - logging.getLogger("curtsies").setLevel(level) - logging.getLogger("bpython").setLevel(level) - if options.log: - handler = logging.FileHandler(filename="bpython.log") - logging.getLogger("curtsies").addHandler(handler) - logging.getLogger("curtsies").propagate = False - logging.getLogger("bpython").addHandler(handler) - logging.getLogger("bpython").propagate = False interp = None paste = None diff --git a/doc/sphinx/source/man-bpython.rst b/doc/sphinx/source/man-bpython.rst index 463aa6018..fe37a25fe 100644 --- a/doc/sphinx/source/man-bpython.rst +++ b/doc/sphinx/source/man-bpython.rst @@ -57,12 +57,12 @@ The following options are supported by all frontends: exiting. The PYTHONSTARTUP file is not read. -q, --quiet Do not flush the output to stdout. -V, --version Print :program:`bpython`'s version and exit. +-l , --log-level= Set logging level +-L , --log-output= Set log output file In addition to the above options, :program:`bpython` also supports the following options: --L, --log Write debugging messages to the file bpython.log. Use - -LL for more verbose logging. -p file, --paste=file Paste in the contents of a file at startup. In addition to the common options, :program:`bpython-urwid` also supports the From 7204eb5f580f275254c10a550d846a992a63c348 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 14 Feb 2021 17:28:31 +0100 Subject: [PATCH 1223/1650] Improve docstring --- bpython/args.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 9618cc0c2..7cba9a6d9 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -74,9 +74,12 @@ def callback(group): parse( ['-i', '-m', 'foo.py'], - ('Front end-specific options', - 'A full description of what these options are for', - callback)) + ( + 'Front end-specific options', + 'A full description of what these options are for', + callback + ), + ) Return a tuple of (config, options, exec_args) wherein "config" is the From 964f42c160145e89d04a05887a4285b82ddf7c74 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 14 Feb 2021 17:29:36 +0100 Subject: [PATCH 1224/1650] Translate version and copyright banner --- bpython/args.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 7cba9a6d9..f670f8dae 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -46,7 +46,7 @@ def error(self, msg): def version_banner(base="bpython"): - return "{} version {} on top of Python {} {}".format( + return _("{} version {} on top of Python {} {}").format( base, __version__, sys.version.split()[0], @@ -55,7 +55,7 @@ def version_banner(base="bpython"): def copyright_banner(): - return "{} See AUTHORS.rst for details.".format(__copyright__) + return _("{} See AUTHORS.rst for details.").format(__copyright__) def parse(args, extras=None, ignore_stdin=False): From 9a0ad312c964e40ff830ed9e4a136f7d5d5405ee Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 14 Feb 2021 17:33:53 +0100 Subject: [PATCH 1225/1650] Update translations --- bpython/translations/bpython.pot | 62 +++++--- .../translations/de/LC_MESSAGES/bpython.po | 143 ++++++++++-------- .../translations/es_ES/LC_MESSAGES/bpython.po | 60 +++++--- .../translations/fr_FR/LC_MESSAGES/bpython.po | 60 +++++--- .../translations/it_IT/LC_MESSAGES/bpython.po | 60 +++++--- .../translations/nl_NL/LC_MESSAGES/bpython.po | 60 +++++--- 6 files changed, 257 insertions(+), 188 deletions(-) diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 7b23845d7..24c9cdd8a 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.22.dev8\n" +"Project-Id-Version: bpython 0.22.dev28\n" "Report-Msgid-Bugs-To: https://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-29 22:59+0100\n" +"POT-Creation-Date: 2021-02-14 17:29+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,7 +17,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:91 +#: bpython/args.py:49 +msgid "{} version {} on top of Python {} {}" +msgstr "" + +#: bpython/args.py:58 +msgid "{} See AUTHORS.rst for details." +msgstr "" + +#: bpython/args.py:95 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -25,23 +33,31 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:101 +#: bpython/args.py:105 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:107 +#: bpython/args.py:111 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:113 +#: bpython/args.py:117 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:119 +#: bpython/args.py:123 msgid "Print version and exit." msgstr "" #: bpython/args.py:130 +msgid "Set log level for logging" +msgstr "" + +#: bpython/args.py:135 +msgid "Log output file" +msgstr "" + +#: bpython/args.py:146 msgid "File to execute and additional arguments passed on to the executed script." msgstr "" @@ -81,18 +97,14 @@ msgid "" msgstr "" #: bpython/curtsies.py:136 -msgid "log debug messages to bpython.log" -msgstr "" - -#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:148 +#: bpython/curtsies.py:142 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:143 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" @@ -279,55 +291,55 @@ msgid "" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:324 +#: bpython/curtsiesfrontend/repl.py:325 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:326 +#: bpython/curtsiesfrontend/repl.py:327 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:663 +#: bpython/curtsiesfrontend/repl.py:664 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:681 +#: bpython/curtsiesfrontend/repl.py:682 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:990 +#: bpython/curtsiesfrontend/repl.py:991 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1005 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1015 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1032 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1985 +#: bpython/curtsiesfrontend/repl.py:1986 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 70684700b..95bd6cc84 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,45 +7,62 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-29 22:59+0100\n" -"PO-Revision-Date: 2021-01-29 22:58+0100\n" +"POT-Creation-Date: 2021-02-14 17:29+0100\n" +"PO-Revision-Date: 2021-02-14 17:31+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: de\n" "Language-Team: de \n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" +"X-Generator: Poedit 2.4.2\n" -#: bpython/args.py:91 +#: bpython/args.py:49 +msgid "{} version {} on top of Python {} {}" +msgstr "{} Version {} mit Python {} {}" + +#: bpython/args.py:58 +msgid "{} See AUTHORS.rst for details." +msgstr "{} Siehe AUTHORS.rst für mehr Details." + +#: bpython/args.py:95 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back " -"to the regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back to the " +"regular Python interpreter." msgstr "" "Verwendung: %(prog)s [Optionen] [Datei [Argumente]]\n" -"Hinweis: Wenn bpython Argumente übergeben bekommt, die nicht verstanden " -"werden, wird der normale Python Interpreter ausgeführt." +"Hinweis: Wenn bpython Argumente übergeben bekommt, die nicht verstanden werden, " +"wird der normale Python Interpreter ausgeführt." -#: bpython/args.py:101 +#: bpython/args.py:105 msgid "Use CONFIG instead of default config file." msgstr "Verwende CONFIG antatt der standardmäßigen Konfigurationsdatei." -#: bpython/args.py:107 +#: bpython/args.py:111 msgid "Drop to bpython shell after running file instead of exiting." msgstr "Verbleibe in bpython nach dem Ausführen der Datei." -#: bpython/args.py:113 +#: bpython/args.py:117 msgid "Don't flush the output to stdout." msgstr "Gib Ausgabe beim Beenden nicht ernaut auf stdout aus." -#: bpython/args.py:119 +#: bpython/args.py:123 msgid "Print version and exit." msgstr "Zeige Versionsinformationen an und beende." #: bpython/args.py:130 +msgid "Set log level for logging" +msgstr "Log-Stufe" + +#: bpython/args.py:135 +msgid "Log output file" +msgstr "Datei für Ausgabe von Log-Nachrichten" + +#: bpython/args.py:146 msgid "File to execute and additional arguments passed on to the executed script." msgstr "" @@ -79,27 +96,23 @@ msgstr "Quellcode anzeigen" #: bpython/cli.py:1943 msgid "" -"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " -"This backend has been deprecated in version 0.19 and might disappear in a" -" future version." +"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. This " +"backend has been deprecated in version 0.19 and might disappear in a future " +"version." msgstr "" -"ACHTUNG: `bpython-cli` wird verwendet, die curses Implementierung von " -"`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " -"unterstützt und wird in einer zukünftigen Version entfernt werden." +"ACHTUNG: `bpython-cli` wird verwendet, die curses Implementierung von `bpython`. " +"Diese Implementierung wird ab Version 0.19 nicht mehr aktiv unterstützt und wird " +"in einer zukünftigen Version entfernt werden." #: bpython/curtsies.py:136 -msgid "log debug messages to bpython.log" -msgstr "Zeichne debug Nachrichten in bpython.log auf" - -#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:148 +#: bpython/curtsies.py:142 msgid "curtsies arguments" msgstr "Argumente für curtsies" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:143 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "Zusätzliche Argumente spezifisch für die curtsies-basierte REPL." @@ -227,8 +240,8 @@ msgstr "Pastebin URL: %s" #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -"Wie viele Zeilen rückgängig machen? (Rückgängigmachen wird bis zu ~%.1f " -"Sekunden brauchen) [1]" +"Wie viele Zeilen rückgängig machen? (Rückgängigmachen wird bis zu ~%.1f Sekunden " +"brauchen) [1]" #: bpython/repl.py:964 bpython/repl.py:968 msgid "Undo canceled" @@ -244,14 +257,13 @@ msgstr[1] "Mache %d Zeilen rückgängig... (ungefähr %.1f Sekunden)" #: bpython/repl.py:1147 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " -"werden? (j/N)" +"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt werden? (j/N)" #: bpython/repl.py:1175 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -"bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " -"Änderungen übernommen werden." +"bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die Änderungen " +"übernommen werden." #: bpython/repl.py:1181 #, python-format @@ -262,8 +274,8 @@ msgstr "Fehler beim Bearbeiten der Konfigurationsdatei: %s" #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -" <%s> Rückgängigmachen <%s> Speichern <%s> Pastebin <%s> Pager <%s> " -"Quellcode anzeigen " +" <%s> Rückgängigmachen <%s> Speichern <%s> Pastebin <%s> Pager <%s> Quellcode " +"anzeigen " #: bpython/urwid.py:1114 msgid "Run twisted reactor." @@ -279,11 +291,11 @@ msgstr "Liste verfügbare reactors für -r auf." #: bpython/urwid.py:1132 msgid "" -"twistd plugin to run (use twistd for a list). Use \"--\" to pass further " -"options to the plugin." +"twistd plugin to run (use twistd for a list). Use \"--\" to pass further options " +"to the plugin." msgstr "" -"Auszuführendes twistd Plugin (starte twistd für eine Liste). Verwende " -"\"--\" um Optionen an das Plugin zu übergeben." +"Auszuführendes twistd Plugin (starte twistd für eine Liste). Verwende \"--\" um " +"Optionen an das Plugin zu übergeben." #: bpython/urwid.py:1141 msgid "Port to run an eval server on (forces Twisted)." @@ -291,91 +303,88 @@ msgstr "" #: bpython/urwid.py:1335 msgid "" -"WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " -"This backend has been deprecated in version 0.19 and might disappear in a" -" future version." +"WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. This " +"backend has been deprecated in version 0.19 and might disappear in a future " +"version." msgstr "" "ACHTUNG: `bpython-urwid` wird verwendet, die curses Implementierung von " "`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " "unterstützt und wird in einer zukünftigen Version entfernt werden." -#: bpython/curtsiesfrontend/repl.py:324 +#: bpython/curtsiesfrontend/repl.py:325 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" -#: bpython/curtsiesfrontend/repl.py:326 +#: bpython/curtsiesfrontend/repl.py:327 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#: bpython/curtsiesfrontend/repl.py:663 +#: bpython/curtsiesfrontend/repl.py:664 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" -#: bpython/curtsiesfrontend/repl.py:681 +#: bpython/curtsiesfrontend/repl.py:682 #, python-format msgid "Reloaded at %s because %s modified." msgstr "Bei %s neugeladen, da %s modifiziert wurde." -#: bpython/curtsiesfrontend/repl.py:990 +#: bpython/curtsiesfrontend/repl.py:991 msgid "Session not reevaluated because it was not edited" msgstr "Die Sitzung wurde nicht neu ausgeführt, da sie nicht berabeitet wurde" -#: bpython/curtsiesfrontend/repl.py:1005 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "Die Sitzung wurde nicht neu ausgeführt, da die gespeicherte Datei leer war" -#: bpython/curtsiesfrontend/repl.py:1015 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "Sitzung bearbeitet und neu ausgeführt" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "Bei %s vom Benutzer neu geladen." -#: bpython/curtsiesfrontend/repl.py:1032 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "Automatisches Neuladen deaktiviert." -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "Automatisches Neuladen ist aktiv; beobachte Dateiänderungen..." -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -"Automatisches Neuladen ist nicht verfügbar da watchdog nicht installiert " -"ist." +"Automatisches Neuladen ist nicht verfügbar da watchdog nicht installiert ist." -#: bpython/curtsiesfrontend/repl.py:1985 +#: bpython/curtsiesfrontend/repl.py:1986 msgid "" "\n" "Thanks for using bpython!\n" "\n" -"See http://bpython-interpreter.org/ for more information and http://docs" -".bpython-interpreter.org/ for docs.\n" +"See http://bpython-interpreter.org/ for more information and http://docs.bpython-" +"interpreter.org/ for docs.\n" "Please report issues at https://github.com/bpython/bpython/issues\n" "\n" "Features:\n" "Try using undo ({config.undo_key})!\n" -"Edit the current line ({config.edit_current_block_key}) or the entire " -"session ({config.external_editor_key}) in an external editor. (currently " -"{config.editor})\n" -"Save sessions ({config.save_key}) or post them to pastebins " -"({config.pastebin_key})! Current pastebin helper: " -"{config.pastebin_helper}\n" -"Reload all modules and rerun session ({config.reimport_key}) to test out " -"changes to a module.\n" -"Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute " -"the current session when a module you've imported is modified.\n" +"Edit the current line ({config.edit_current_block_key}) or the entire session " +"({config.external_editor_key}) in an external editor. (currently {config." +"editor})\n" +"Save sessions ({config.save_key}) or post them to pastebins ({config." +"pastebin_key})! Current pastebin helper: {config.pastebin_helper}\n" +"Reload all modules and rerun session ({config.reimport_key}) to test out changes " +"to a module.\n" +"Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute the " +"current session when a module you've imported is modified.\n" "\n" "bpython -i your_script.py runs a file in interactive mode\n" "bpython -t your_script.py pastes the contents of a file into the session\n" "\n" -"A config file at {config.config_path} customizes keys and behavior of " -"bpython.\n" +"A config file at {config.config_path} customizes keys and behavior of bpython.\n" "You can also set which pastebin helper and which external editor to use.\n" "See {example_config_url} for an example config file.\n" "Press {config.edit_config_key} to edit this config file.\n" diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 3d5704ae7..b877fd1bd 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-29 22:59+0100\n" +"POT-Creation-Date: 2021-02-14 17:29+0100\n" "PO-Revision-Date: 2020-10-29 12:22+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: es_ES\n" @@ -18,7 +18,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:91 +#: bpython/args.py:49 +msgid "{} version {} on top of Python {} {}" +msgstr "" + +#: bpython/args.py:58 +msgid "{} See AUTHORS.rst for details." +msgstr "" + +#: bpython/args.py:95 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -26,23 +34,31 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:101 +#: bpython/args.py:105 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:107 +#: bpython/args.py:111 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:113 +#: bpython/args.py:117 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:119 +#: bpython/args.py:123 msgid "Print version and exit." msgstr "" #: bpython/args.py:130 +msgid "Set log level for logging" +msgstr "" + +#: bpython/args.py:135 +msgid "Log output file" +msgstr "" + +#: bpython/args.py:146 msgid "File to execute and additional arguments passed on to the executed script." msgstr "" @@ -82,18 +98,14 @@ msgid "" msgstr "" #: bpython/curtsies.py:136 -msgid "log debug messages to bpython.log" -msgstr "" - -#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:148 +#: bpython/curtsies.py:142 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:143 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" @@ -282,55 +294,55 @@ msgid "" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:324 +#: bpython/curtsiesfrontend/repl.py:325 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:326 +#: bpython/curtsiesfrontend/repl.py:327 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:663 +#: bpython/curtsiesfrontend/repl.py:664 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:681 +#: bpython/curtsiesfrontend/repl.py:682 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:990 +#: bpython/curtsiesfrontend/repl.py:991 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1005 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1015 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1032 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1985 +#: bpython/curtsiesfrontend/repl.py:1986 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index b2b4888d8..d4c4d7758 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-29 22:59+0100\n" +"POT-Creation-Date: 2021-02-14 17:29+0100\n" "PO-Revision-Date: 2020-10-29 12:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: fr_FR\n" @@ -17,7 +17,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:91 +#: bpython/args.py:49 +msgid "{} version {} on top of Python {} {}" +msgstr "" + +#: bpython/args.py:58 +msgid "{} See AUTHORS.rst for details." +msgstr "" + +#: bpython/args.py:95 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -28,25 +36,33 @@ msgstr "" "NOTE: Si bpython ne reconnaît pas un des arguments fournis, " "l'interpréteur Python classique sera lancé" -#: bpython/args.py:101 +#: bpython/args.py:105 msgid "Use CONFIG instead of default config file." msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." -#: bpython/args.py:107 +#: bpython/args.py:111 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" "Aller dans le shell bpython après l'exécution du fichier au lieu de " "quitter." -#: bpython/args.py:113 +#: bpython/args.py:117 msgid "Don't flush the output to stdout." msgstr "Ne pas purger la sortie vers stdout." -#: bpython/args.py:119 +#: bpython/args.py:123 msgid "Print version and exit." msgstr "Afficher la version et quitter." #: bpython/args.py:130 +msgid "Set log level for logging" +msgstr "" + +#: bpython/args.py:135 +msgid "Log output file" +msgstr "" + +#: bpython/args.py:146 msgid "File to execute and additional arguments passed on to the executed script." msgstr "" @@ -86,18 +102,14 @@ msgid "" msgstr "" #: bpython/curtsies.py:136 -msgid "log debug messages to bpython.log" -msgstr "logger les messages de debug dans bpython.log" - -#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:148 +#: bpython/curtsies.py:142 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:143 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" @@ -288,55 +300,55 @@ msgid "" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:324 +#: bpython/curtsiesfrontend/repl.py:325 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" -#: bpython/curtsiesfrontend/repl.py:326 +#: bpython/curtsiesfrontend/repl.py:327 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." -#: bpython/curtsiesfrontend/repl.py:663 +#: bpython/curtsiesfrontend/repl.py:664 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" -#: bpython/curtsiesfrontend/repl.py:681 +#: bpython/curtsiesfrontend/repl.py:682 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:990 +#: bpython/curtsiesfrontend/repl.py:991 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1005 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1015 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1032 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1985 +#: bpython/curtsiesfrontend/repl.py:1986 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index c4e76797b..ddb93cc0c 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-29 22:59+0100\n" +"POT-Creation-Date: 2021-02-14 17:29+0100\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: it_IT\n" @@ -18,7 +18,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:91 +#: bpython/args.py:49 +msgid "{} version {} on top of Python {} {}" +msgstr "" + +#: bpython/args.py:58 +msgid "{} See AUTHORS.rst for details." +msgstr "" + +#: bpython/args.py:95 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -26,23 +34,31 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:101 +#: bpython/args.py:105 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:107 +#: bpython/args.py:111 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:113 +#: bpython/args.py:117 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:119 +#: bpython/args.py:123 msgid "Print version and exit." msgstr "" #: bpython/args.py:130 +msgid "Set log level for logging" +msgstr "" + +#: bpython/args.py:135 +msgid "Log output file" +msgstr "" + +#: bpython/args.py:146 msgid "File to execute and additional arguments passed on to the executed script." msgstr "" @@ -82,18 +98,14 @@ msgid "" msgstr "" #: bpython/curtsies.py:136 -msgid "log debug messages to bpython.log" -msgstr "" - -#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:148 +#: bpython/curtsies.py:142 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:143 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" @@ -280,55 +292,55 @@ msgid "" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:324 +#: bpython/curtsiesfrontend/repl.py:325 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:326 +#: bpython/curtsiesfrontend/repl.py:327 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:663 +#: bpython/curtsiesfrontend/repl.py:664 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:681 +#: bpython/curtsiesfrontend/repl.py:682 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:990 +#: bpython/curtsiesfrontend/repl.py:991 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1005 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1015 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1032 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1985 +#: bpython/curtsiesfrontend/repl.py:1986 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index 87c957390..582442406 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-01-29 22:59+0100\n" +"POT-Creation-Date: 2021-02-14 17:29+0100\n" "PO-Revision-Date: 2020-10-29 12:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: nl_NL\n" @@ -18,7 +18,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:91 +#: bpython/args.py:49 +msgid "{} version {} on top of Python {} {}" +msgstr "" + +#: bpython/args.py:58 +msgid "{} See AUTHORS.rst for details." +msgstr "" + +#: bpython/args.py:95 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -26,23 +34,31 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:101 +#: bpython/args.py:105 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:107 +#: bpython/args.py:111 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:113 +#: bpython/args.py:117 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:119 +#: bpython/args.py:123 msgid "Print version and exit." msgstr "" #: bpython/args.py:130 +msgid "Set log level for logging" +msgstr "" + +#: bpython/args.py:135 +msgid "Log output file" +msgstr "" + +#: bpython/args.py:146 msgid "File to execute and additional arguments passed on to the executed script." msgstr "" @@ -82,18 +98,14 @@ msgid "" msgstr "" #: bpython/curtsies.py:136 -msgid "log debug messages to bpython.log" -msgstr "" - -#: bpython/curtsies.py:142 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:148 +#: bpython/curtsies.py:142 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:149 +#: bpython/curtsies.py:143 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" @@ -280,55 +292,55 @@ msgid "" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:324 +#: bpython/curtsiesfrontend/repl.py:325 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:326 +#: bpython/curtsiesfrontend/repl.py:327 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:663 +#: bpython/curtsiesfrontend/repl.py:664 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:681 +#: bpython/curtsiesfrontend/repl.py:682 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:990 +#: bpython/curtsiesfrontend/repl.py:991 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1005 +#: bpython/curtsiesfrontend/repl.py:1006 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1015 +#: bpython/curtsiesfrontend/repl.py:1016 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1026 +#: bpython/curtsiesfrontend/repl.py:1027 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1032 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1037 +#: bpython/curtsiesfrontend/repl.py:1038 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1043 +#: bpython/curtsiesfrontend/repl.py:1044 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1985 +#: bpython/curtsiesfrontend/repl.py:1986 msgid "" "\n" "Thanks for using bpython!\n" From 8ba24d1a88ace7bbc73b375d71a737c36932d164 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 14 Feb 2021 18:12:56 +0100 Subject: [PATCH 1226/1650] Replace custom clipboard code with pyperclip pyperclip supports Wayland and Windows --- README.rst | 1 + bpython/clipboard.py | 78 -------------------------------------------- bpython/repl.py | 18 ++++++---- setup.cfg | 3 +- 4 files changed, 14 insertions(+), 86 deletions(-) delete mode 100644 bpython/clipboard.py diff --git a/README.rst b/README.rst index 54d2e7d86..eb9112463 100644 --- a/README.rst +++ b/README.rst @@ -163,6 +163,7 @@ Dependencies * babel (optional, for internationalization) * jedi (optional, for experimental multiline completion) * watchdog (optional, for monitoring imported modules for changes) +* pyperclip (optional, for copying to the clipboard) bpython-urwid ------------- diff --git a/bpython/clipboard.py b/bpython/clipboard.py deleted file mode 100644 index be17566f4..000000000 --- a/bpython/clipboard.py +++ /dev/null @@ -1,78 +0,0 @@ -# The MIT License -# -# Copyright (c) 2015 Sebastian Ramacher -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - - -import subprocess -import os -import platform -from locale import getpreferredencoding - - -class CopyFailed(Exception): - pass - - -class XClipboard: - """Manage clipboard with xclip.""" - - def copy(self, content): - process = subprocess.Popen( - ["xclip", "-i", "-selection", "clipboard"], stdin=subprocess.PIPE - ) - process.communicate(content.encode(getpreferredencoding())) - if process.returncode != 0: - raise CopyFailed() - - -class OSXClipboard: - """Manage clipboard with pbcopy.""" - - def copy(self, content): - process = subprocess.Popen(["pbcopy", "w"], stdin=subprocess.PIPE) - process.communicate(content.encode(getpreferredencoding())) - if process.returncode != 0: - raise CopyFailed() - - -def command_exists(command): - process = subprocess.Popen( - ["which", command], stderr=subprocess.STDOUT, stdout=subprocess.PIPE - ) - process.communicate() - - return process.returncode == 0 - - -def get_clipboard(): - """Get best clipboard handling implementation for current system.""" - - if platform.system() == "Darwin": - if command_exists("pbcopy"): - return OSXClipboard() - if ( - platform.system() in ("Linux", "FreeBSD", "OpenBSD") - and os.getenv("DISPLAY") is not None - ): - if command_exists("xclip"): - return XClipboard() - - return None diff --git a/bpython/repl.py b/bpython/repl.py index 7c827602a..62e008101 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -36,12 +36,17 @@ from enum import Enum from itertools import takewhile from pathlib import Path +from pygments.lexers import Python3Lexer +from pygments.token import Token from types import ModuleType -from pygments.token import Token -from pygments.lexers import Python3Lexer +have_pyperclip = True +try: + import pyperclip +except ImportError: + have_pyperclip = False + from . import autocomplete, inspection, simpleeval -from .clipboard import get_clipboard, CopyFailed from .config import getpreferredencoding from .formatter import Parenthesis from .history import History @@ -430,7 +435,6 @@ def __init__(self, interp, config): # Necessary to fix mercurial.ui.ui expecting sys.stderr to have this # attribute self.closed = False - self.clipboard = get_clipboard() if self.config.hist_file.exists(): try: @@ -862,14 +866,14 @@ def write2file(self): def copy2clipboard(self): """Copy current content to clipboard.""" - if self.clipboard is None: + if not have_pyperclip: self.interact.notify(_("No clipboard available.")) return content = self.get_session_formatted_for_file() try: - self.clipboard.copy(content) - except CopyFailed: + pyperclip.copy(content) + except pyperclip.PyperclipException: self.interact.notify(_("Could not copy to clipboard.")) else: self.interact.notify(_("Copied content to clipboard.")) diff --git a/setup.cfg b/setup.cfg index 7dfb1d58c..8b6177591 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,9 +28,10 @@ install_requires = pyxdg [options.extras_require] +clipboard = pyperclip +jedi = jedi >= 0.16 urwid = urwid watch = watchdog -jedi = jedi >= 0.16 [options.entry_points] console_scripts = From 624116ebe3e768f6935ac65d5bab0f495c80a65b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 14 Feb 2021 21:16:49 +0100 Subject: [PATCH 1227/1650] Turn FileLock into a wrapper function Also make sure that for UnixFileLock fcntl.LOCK_EX is always set. --- bpython/filelock.py | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/bpython/filelock.py b/bpython/filelock.py index 72018f832..5a931739e 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -1,6 +1,6 @@ # The MIT License # -# Copyright (c) 2015-2019 Sebastian Ramacher +# Copyright (c) 2015-2021 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -20,20 +20,17 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - +has_fcntl = True try: import fcntl import errno - - has_fcntl = True except ImportError: has_fcntl = False +has_msvcrt = True try: import msvcrt import os - - has_msvcrt = True except ImportError: has_msvcrt = False @@ -41,7 +38,7 @@ class BaseLock: """Base class for file locking""" - def __init__(self, fileobj, mode=None, filename=None): + def __init__(self, fileobj=None): self.fileobj = fileobj self.locked = False @@ -67,12 +64,9 @@ def __del__(self): class UnixFileLock(BaseLock): """Simple file locking for Unix using fcntl""" - def __init__(self, fileobj, mode=None, filename=None): + def __init__(self, fileobj, mode=0): super().__init__(fileobj) - - if mode is None: - mode = fcntl.LOCK_EX - self.mode = mode + self.mode = mode | fcntl.LOCK_EX def acquire(self): try: @@ -90,8 +84,8 @@ def release(self): class WindowsFileLock(BaseLock): """Simple file locking for Windows using msvcrt""" - def __init__(self, fileobj, mode=None, filename=None): - super().__init__(None) + def __init__(self, filename): + super().__init__() self.filename = f"{filename}.lock" def acquire(self): @@ -117,11 +111,12 @@ def release(self): pass -if has_fcntl: - FileLock = UnixFileLock -elif has_msvcrt: - FileLock = WindowsFileLock -else: - FileLock = BaseLock +def FileLock(fileobj, mode=0, filename=None): + if has_fcntl: + return UnixFileLock(fileobj, mode) + elif has_msvcrt: + return WindowsFileLock(filename) + return BaseLock(fileobj) + # vim: sw=4 ts=4 sts=4 ai et From 34f0e700082bf79223689ff120dddf4e46442129 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 10 Mar 2021 23:32:58 +0100 Subject: [PATCH 1228/1650] Iterate over all completers until the first one with succeeding locate is found (fixes #879) --- bpython/autocomplete.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 477b9651c..f4424f7c8 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -267,7 +267,10 @@ def __init__(self, completers, mode=AutocompleteModes.SIMPLE): super().__init__(True, mode) def locate(self, current_offset, line): - return self._completers[0].locate(current_offset, line) + for completer in self._completers: + return_value = completer.locate(current_offset, line) + if return_value is not None: + return return_value def format(self, word): return self._completers[0].format(word) From 2f4e69a92595321e6ab17ae8e24ddc1f6691e9f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 11 Mar 2021 23:48:01 +0100 Subject: [PATCH 1229/1650] Assume utf8 encoding (fixes #888) If there is no encoding specified, the default encoding in Python 3 is utf8. --- bpython/inspection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index bf039e612..e4d8abe06 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -326,7 +326,7 @@ def get_encoding(obj): m = get_encoding_line_re.search(line) if m: return m.group(1) - return "ascii" + return "utf8" def get_encoding_file(fname): @@ -337,7 +337,7 @@ def get_encoding_file(fname): match = get_encoding_line_re.search(line) if match: return match.group(1) - return "ascii" + return "utf8" def getattr_safe(obj, name): From 72e1c4b4bbb759f00d62472f241748193a1cd968 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 16 Mar 2021 22:56:49 +0100 Subject: [PATCH 1230/1650] Also handle function __doc__ where the signature is split over two or more lines --- .github/workflows/build.yaml | 3 ++- bpython/inspection.py | 11 +++++++---- bpython/test/test_inspection.py | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9af5bb048..f01bcb236 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -26,7 +26,8 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install pytest pytest-cov urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" + pip install urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" + pip install pytest pytest-cov numpy - name: Build with Python ${{ matrix.python-version }} run: | python setup.py build diff --git a/bpython/inspection.py b/bpython/inspection.py index e4d8abe06..480f59275 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -24,6 +24,7 @@ import inspect import keyword import pydoc +import re from collections import namedtuple from pygments.token import Token @@ -173,7 +174,9 @@ def fixlongargs(f, argspec): argspec[3] = values -getpydocspec_re = LazyReCompile(r"([a-zA-Z_][a-zA-Z0-9_]*?)\((.*?)\)") +getpydocspec_re = LazyReCompile( + r"([a-zA-Z_][a-zA-Z0-9_]*?)\((.*?)\)", re.DOTALL +) def getpydocspec(f, func): @@ -189,10 +192,10 @@ def getpydocspec(f, func): if not hasattr_safe(f, "__name__") or s.groups()[0] != f.__name__: return None - args = list() - defaults = list() + args = [] + defaults = [] varargs = varkwargs = None - kwonly_args = list() + kwonly_args = [] kwonly_defaults = dict() for arg in s.group(2).split(","): arg = arg.strip() diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 885c0dbe1..c34d43f60 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -6,6 +6,11 @@ from bpython.test.fodder import encoding_latin1 from bpython.test.fodder import encoding_utf8 +try: + import numpy +except ImportError: + numpy = None + foo_ascii_only = '''def foo(): """Test""" @@ -113,6 +118,15 @@ def test_get_source_file(self): ) self.assertEqual(encoding, "utf-8") + @unittest.skipUnless(numpy is not None, "requires numpy") + def test_getfuncprops_numpy_array(self): + props = inspection.getfuncprops("array", numpy.array) + + self.assertEqual(props.func, "array") + # This check might need an update in the future, but at least numpy >= 1.18 has + # np.array(object, dtype=None, *, ...). + self.assertEqual(props.argspec.args, ["object", "dtype"]) + class A: a = "a" From 7ed578e16a60bdd58db2dfcca675ba4e14bc8eea Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 16 Mar 2021 23:21:54 +0100 Subject: [PATCH 1231/1650] Run pytest with -v --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f01bcb236..b877487a7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -37,7 +37,7 @@ jobs: python setup.py build_sphinx_man - name: Test with pytest run: | - pytest --cov=bpython --cov-report=xml + pytest --cov=bpython --cov-report=xml -v - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 env: From 30018e4274122ed5b272eec44e319a49eeb33e36 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 16 Mar 2021 23:39:43 +0100 Subject: [PATCH 1232/1650] Treat ... in __doc__ as separator between varargs and kwonly args --- bpython/inspection.py | 3 +++ bpython/test/test_inspection.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/bpython/inspection.py b/bpython/inspection.py index 480f59275..222def51c 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -203,6 +203,9 @@ def getpydocspec(f, func): varkwargs = arg[2:] elif arg.startswith("*"): varargs = arg[1:] + elif arg == "...": + # At least print denotes "..." as separator between varargs and kwonly args. + varargs = "" else: arg, _, default = arg.partition("=") if varargs is not None: diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index c34d43f60..bb9d24c60 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -118,6 +118,17 @@ def test_get_source_file(self): ) self.assertEqual(encoding, "utf-8") + def test_getfuncprops_print(self): + props = inspection.getfuncprops("print", print) + + self.assertEqual(props.func, "print") + self.assertIn("end", props.argspec.kwonly) + self.assertIn("file", props.argspec.kwonly) + self.assertIn("flush", props.argspec.kwonly) + self.assertIn("sep", props.argspec.kwonly) + self.assertEqual(props.argspec.kwonly_defaults["file"], "sys.stdout") + self.assertEqual(props.argspec.kwonly_defaults["flush"], "False") + @unittest.skipUnless(numpy is not None, "requires numpy") def test_getfuncprops_numpy_array(self): props = inspection.getfuncprops("array", numpy.array) From d4577d383b124535f1b155ff32bf60e57bcbf94d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 17 Mar 2021 11:46:14 +0100 Subject: [PATCH 1233/1650] Handle locale specific confirmation --- bpython/curtsiesfrontend/interaction.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index f7265dcc8..a6ad866e6 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -2,6 +2,7 @@ import time import curtsies.events as events +from bpython.translations import _ from bpython.repl import Interaction as BpythonInteraction from bpython.curtsiesfrontend.events import RefreshRequestEvent from bpython.curtsiesfrontend.manual_readline import edit_keys @@ -103,7 +104,7 @@ def process_event(self, e): self.escape() self.request_context.switch(line) elif self.in_confirm: - if e in ("y", "Y"): + if e.lower() == _("y"): self.request_context.switch(True) else: self.request_context.switch(False) From a257b3f98b2e2c0a53d4fa4474ea77714bc014ed Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 17 Mar 2021 11:52:26 +0100 Subject: [PATCH 1234/1650] Check numpy version --- bpython/test/test_inspection.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index bb9d24c60..7c04c521e 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -129,7 +129,10 @@ def test_getfuncprops_print(self): self.assertEqual(props.argspec.kwonly_defaults["file"], "sys.stdout") self.assertEqual(props.argspec.kwonly_defaults["flush"], "False") - @unittest.skipUnless(numpy is not None, "requires numpy") + @unittest.skipUnless( + numpy is not None and numpy.__version__ >= "1.18", + "requires numpy >= 1.18", + ) def test_getfuncprops_numpy_array(self): props = inspection.getfuncprops("array", numpy.array) From 0badb7320048fb69f6c74b9f93ef362752a855c9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 17 Mar 2021 12:04:33 +0100 Subject: [PATCH 1235/1650] Mention that comments start with # --- bpython/sample-config | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/bpython/sample-config b/bpython/sample-config index b61dce5b4..287e8a2fc 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -1,11 +1,13 @@ -# This is a standard python config file -# Valid values can be True, False, integer numbers, strings +# This is a standard Python config file. +# Valid values can be True, False, integer numbers, and strings. +# Lines starting with # are treated as comments. +# # By default bpython will look for $XDG_CONFIG_HOME/bpython/config # ($XDG_CONFIG_HOME defaults to ~/.config) or you can specify a file with the -# --config option on the command line +# --config option on the command line. # -# see http://docs.bpython-interpreter.org/configuration.html -# for all configurable options +# See http://docs.bpython-interpreter.org/configuration.html for all +# configurable options. # General section tag [general] From cb05a2de7c69f880820c812acca8c93f4d4d26e2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 20 Mar 2021 15:49:26 +0100 Subject: [PATCH 1236/1650] Turn --config into a Path --- bpython/args.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/args.py b/bpython/args.py index f670f8dae..0a60c86ee 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -30,6 +30,7 @@ import logging import os import sys +from pathlib import Path from . import __version__, __copyright__ from .config import default_config_path, loadini, Struct @@ -102,6 +103,7 @@ def callback(group): parser.add_argument( "--config", default=default_config_path(), + type=Path, help=_("Use CONFIG instead of default config file."), ) parser.add_argument( From 447bede5ab7b51458cacffef001097cc23195737 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 20 Mar 2021 16:44:15 +0100 Subject: [PATCH 1237/1650] Use relative imports --- bpython/curtsiesfrontend/_internal.py | 4 ++-- bpython/curtsiesfrontend/filewatch.py | 2 +- bpython/curtsiesfrontend/interaction.py | 8 ++++---- bpython/curtsiesfrontend/interpreter.py | 4 ++-- bpython/curtsiesfrontend/manual_readline.py | 2 +- bpython/curtsiesfrontend/parse.py | 2 +- bpython/curtsiesfrontend/repl.py | 13 ++++++------- bpython/curtsiesfrontend/replpainter.py | 2 +- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index d5e8cb458..f918f6fc5 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -21,7 +21,7 @@ # THE SOFTWARE. import pydoc -import bpython._internal +from .. import _internal class NopPydocPager: @@ -36,7 +36,7 @@ def __call__(self, text): return None -class _Helper(bpython._internal._Helper): +class _Helper(_internal._Helper): def __init__(self, repl=None): self._repl = repl pydoc.pager = self.pager diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 676736737..6657fac17 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -1,7 +1,7 @@ import os from collections import defaultdict -from bpython import importcompletion +from .. import importcompletion try: from watchdog.observers import Observer diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index a6ad866e6..dd9b13a92 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -2,10 +2,10 @@ import time import curtsies.events as events -from bpython.translations import _ -from bpython.repl import Interaction as BpythonInteraction -from bpython.curtsiesfrontend.events import RefreshRequestEvent -from bpython.curtsiesfrontend.manual_readline import edit_keys +from ..translations import _ +from ..repl import Interaction as BpythonInteraction +from ..curtsiesfrontend.events import RefreshRequestEvent +from ..curtsiesfrontend.manual_readline import edit_keys class StatusBar(BpythonInteraction): diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 6d62c65a7..a48bc429a 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -6,8 +6,8 @@ from pygments.formatter import Formatter from pygments.lexers import get_lexer_by_name -from bpython.curtsiesfrontend.parse import parse -from bpython.repl import Interpreter as ReplInterpreter +from ..curtsiesfrontend.parse import parse +from ..repl import Interpreter as ReplInterpreter default_colors = { diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index d2efe77bb..15aea35c2 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -4,7 +4,7 @@ and the cursor location based on http://www.bigsmoke.us/readline/shortcuts""" -from bpython.lazyre import LazyReCompile +from ..lazyre import LazyReCompile import inspect diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index dacb5d6a0..6076a027a 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -8,7 +8,7 @@ ) from functools import partial -from bpython.lazyre import LazyReCompile +from ..lazyre import LazyReCompile COLORS = CURTSIES_COLORS + ("default",) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9a7ee0e08..2a8c499cb 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -31,17 +31,16 @@ from curtsies.configfile_keynames import keymap as key_dispatch from curtsies.input import is_main_thread -from bpython import __version__ -from bpython.repl import ( +from .. import __version__, autocomplete +from ..repl import ( Repl as BpythonRepl, SourceNotFound, LineTypeTranslator as LineType, ) -from bpython.config import getpreferredencoding -from bpython.formatter import BPythonFormatter -from bpython import autocomplete -from bpython.translations import _ -from bpython.pager import get_pager_command +from ..config import getpreferredencoding +from ..formatter import BPythonFormatter +from ..translations import _ +from ..pager import get_pager_command from . import events as bpythonevents, sitefix, replpainter as paint from .coderunner import ( diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index d7de5fd0e..00675451d 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -5,7 +5,7 @@ from curtsies.formatstring import linesplit from curtsies.fmtfuncs import bold -from bpython.curtsiesfrontend.parse import func_for_letter +from .parse import func_for_letter logger = logging.getLogger(__name__) From ae67b5a0b46604edc480b88b318685092eecdc37 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Mar 2021 18:45:10 +0200 Subject: [PATCH 1238/1650] Add type annotations --- bpython/filelock.py | 47 ++++++++++++++++----------- bpython/history.py | 78 ++++++++++++++++++++++++++++++--------------- 2 files changed, 81 insertions(+), 44 deletions(-) diff --git a/bpython/filelock.py b/bpython/filelock.py index 5a931739e..3d77c439c 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -20,6 +20,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +from typing import Optional, Type, IO +from types import TracebackType + has_fcntl = True try: import fcntl @@ -38,25 +41,29 @@ class BaseLock: """Base class for file locking""" - def __init__(self, fileobj=None): - self.fileobj = fileobj + def __init__(self) -> None: self.locked = False - def acquire(self): + def acquire(self) -> None: pass - def release(self): + def release(self) -> None: pass - def __enter__(self): + def __enter__(self) -> "BaseLock": self.acquire() return self - def __exit__(self, *args): + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: if self.locked: self.release() - def __del__(self): + def __del__(self) -> None: if self.locked: self.release() @@ -64,11 +71,12 @@ def __del__(self): class UnixFileLock(BaseLock): """Simple file locking for Unix using fcntl""" - def __init__(self, fileobj, mode=0): - super().__init__(fileobj) + def __init__(self, fileobj, mode: int = 0) -> None: + super().__init__() + self.fileobj = fileobj self.mode = mode | fcntl.LOCK_EX - def acquire(self): + def acquire(self) -> None: try: fcntl.flock(self.fileobj, self.mode) self.locked = True @@ -76,7 +84,7 @@ def acquire(self): if e.errno != errno.ENOLCK: raise e - def release(self): + def release(self) -> None: self.locked = False fcntl.flock(self.fileobj, fcntl.LOCK_UN) @@ -84,11 +92,12 @@ def release(self): class WindowsFileLock(BaseLock): """Simple file locking for Windows using msvcrt""" - def __init__(self, filename): + def __init__(self, filename: str) -> None: super().__init__() self.filename = f"{filename}.lock" + self.fileobj = -1 - def acquire(self): + def acquire(self) -> None: # create a lock file and lock it self.fileobj = os.open( self.filename, os.O_RDWR | os.O_CREAT | os.O_TRUNC @@ -97,13 +106,13 @@ def acquire(self): self.locked = True - def release(self): + def release(self) -> None: self.locked = False # unlock lock file and remove it msvcrt.locking(self.fileobj, msvcrt.LK_UNLCK, 1) os.close(self.fileobj) - self.fileobj = None + self.fileobj = -1 try: os.remove(self.filename) @@ -111,12 +120,14 @@ def release(self): pass -def FileLock(fileobj, mode=0, filename=None): +def FileLock( + fileobj: IO, mode: int = 0, filename: Optional[str] = None +) -> BaseLock: if has_fcntl: return UnixFileLock(fileobj, mode) - elif has_msvcrt: + elif has_msvcrt and filename is not None: return WindowsFileLock(filename) - return BaseLock(fileobj) + return BaseLock() # vim: sw=4 ts=4 sts=4 ai et diff --git a/bpython/history.py b/bpython/history.py index 683a062f6..4779eb456 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -1,7 +1,7 @@ # The MIT License # # Copyright (c) 2009 the bpython authors. -# Copyright (c) 2012,2015 Sebastian Ramacher +# Copyright (c) 2012-2021 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -24,6 +24,7 @@ import os import stat from itertools import islice +from typing import Iterable, Optional, List, TextIO from .translations import _ from .filelock import FileLock @@ -32,7 +33,12 @@ class History: """Stores readline-style history and current place in it""" - def __init__(self, entries=None, duplicates=True, hist_size=100): + def __init__( + self, + entries: Optional[Iterable[str]] = None, + duplicates: bool = True, + hist_size: int = 100, + ) -> None: if entries is None: self.entries = [""] else: @@ -45,10 +51,10 @@ def __init__(self, entries=None, duplicates=True, hist_size=100): self.duplicates = duplicates self.hist_size = hist_size - def append(self, line): + def append(self, line: str) -> None: self.append_to(self.entries, line) - def append_to(self, entries, line): + def append_to(self, entries: List[str], line: str) -> None: line = line.rstrip("\n") if line: if not self.duplicates: @@ -60,15 +66,19 @@ def append_to(self, entries, line): pass entries.append(line) - def first(self): + def first(self) -> str: """Move back to the beginning of the history.""" if not self.is_at_end: self.index = len(self.entries) return self.entries[-self.index] def back( - self, start=True, search=False, target=None, include_current=False - ): + self, + start: bool = True, + search: bool = False, + target: Optional[str] = None, + include_current: bool = False, + ) -> str: """Move one step back in the history.""" if target is None: target = self.saved_line @@ -84,15 +94,17 @@ def back( return self.entry @property - def entry(self): + def entry(self) -> str: """The current entry, which may be the saved line""" return self.entries[-self.index] if self.index else self.saved_line @property - def entries_by_index(self): + def entries_by_index(self) -> List[str]: return list(reversed(self.entries + [self.saved_line])) - def find_match_backward(self, search_term, include_current=False): + def find_match_backward( + self, search_term: str, include_current: bool = False + ) -> int: add = 0 if include_current else 1 start = self.index + add for idx, val in enumerate(islice(self.entries_by_index, start, None)): @@ -100,7 +112,9 @@ def find_match_backward(self, search_term, include_current=False): return idx + add return 0 - def find_partial_match_backward(self, search_term, include_current=False): + def find_partial_match_backward( + self, search_term: str, include_current: bool = False + ) -> int: add = 0 if include_current else 1 start = self.index + add for idx, val in enumerate(islice(self.entries_by_index, start, None)): @@ -109,8 +123,12 @@ def find_partial_match_backward(self, search_term, include_current=False): return 0 def forward( - self, start=True, search=False, target=None, include_current=False - ): + self, + start: bool = True, + search: bool = False, + target: Optional[str] = None, + include_current: bool = False, + ) -> str: """Move one step forward in the history.""" if target is None: target = self.saved_line @@ -128,7 +146,9 @@ def forward( self.index = 0 return self.saved_line - def find_match_forward(self, search_term, include_current=False): + def find_match_forward( + self, search_term: str, include_current: bool = False + ) -> int: add = 0 if include_current else 1 end = max(0, self.index - (1 - add)) for idx in range(end): @@ -137,7 +157,9 @@ def find_match_forward(self, search_term, include_current=False): return idx + (0 if include_current else 1) return self.index - def find_partial_match_forward(self, search_term, include_current=False): + def find_partial_match_forward( + self, search_term: str, include_current: bool = False + ) -> int: add = 0 if include_current else 1 end = max(0, self.index - (1 - add)) for idx in range(end): @@ -146,40 +168,40 @@ def find_partial_match_forward(self, search_term, include_current=False): return idx + add return self.index - def last(self): + def last(self) -> str: """Move forward to the end of the history.""" if not self.is_at_start: self.index = 0 return self.entries[0] @property - def is_at_end(self): + def is_at_end(self) -> bool: return self.index >= len(self.entries) or self.index == -1 @property - def is_at_start(self): + def is_at_start(self) -> bool: return self.index == 0 - def enter(self, line): + def enter(self, line: str) -> None: if self.index == 0: self.saved_line = line - def reset(self): + def reset(self) -> None: self.index = 0 self.saved_line = "" - def load(self, filename, encoding): + def load(self, filename: str, encoding: str) -> None: with open(filename, encoding=encoding, errors="ignore") as hfile: with FileLock(hfile, filename=filename): self.entries = self.load_from(hfile) - def load_from(self, fd): - entries = [] + def load_from(self, fd: TextIO) -> List[str]: + entries: List[str] = [] for line in fd: self.append_to(entries, line) return entries if len(entries) else [""] - def save(self, filename, encoding, lines=0): + def save(self, filename: str, encoding: str, lines: int = 0) -> None: fd = os.open( filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, @@ -189,14 +211,18 @@ def save(self, filename, encoding, lines=0): with FileLock(hfile, filename=filename): self.save_to(hfile, self.entries, lines) - def save_to(self, fd, entries=None, lines=0): + def save_to( + self, fd: TextIO, entries: Optional[List[str]] = None, lines: int = 0 + ) -> None: if entries is None: entries = self.entries for line in entries[-lines:]: fd.write(line) fd.write("\n") - def append_reload_and_write(self, s, filename, encoding): + def append_reload_and_write( + self, s: str, filename: str, encoding: str + ) -> None: if not self.hist_size: return self.append(s) From d89793e365da873edaea5ef9eb9c5d4cca59eb07 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Mar 2021 22:20:57 +0200 Subject: [PATCH 1239/1650] Use f-strings --- bpython/keys.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bpython/keys.py b/bpython/keys.py index 3da060d2c..8956ba648 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -52,14 +52,14 @@ def __setitem__(self, key, value): # fill dispatch with letters for c in string.ascii_lowercase: - cli_key_dispatch["C-%s" % c] = ( + cli_key_dispatch[f"C-{c}"] = ( chr(string.ascii_lowercase.index(c) + 1), - "^%s" % c.upper(), + f"^{c.upper()}", ) for c in string.ascii_lowercase: - urwid_key_dispatch["C-%s" % c] = "ctrl %s" % c - urwid_key_dispatch["M-%s" % c] = "meta %s" % c + urwid_key_dispatch[f"C-{c}"] = f"ctrl {c}" + urwid_key_dispatch[f"M-{c}"] = f"meta {c}" # fill dispatch with cool characters cli_key_dispatch["C-["] = (chr(27), "^[") @@ -70,7 +70,7 @@ def __setitem__(self, key, value): # fill dispatch with function keys for x in range(1, 13): - cli_key_dispatch["F%d" % x] = ("KEY_F(%d)" % x,) + cli_key_dispatch[f"F{x}"] = (f"KEY_F({x})",) for x in range(1, 13): - urwid_key_dispatch["F%d" % x] = "f%d" % x + urwid_key_dispatch[f"F{x}"] = f"f{x}" From dc032650e8660cd80f0ce1c3f39bce17b98d9574 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Mar 2021 22:22:33 +0200 Subject: [PATCH 1240/1650] Add type annotations --- bpython/keys.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/bpython/keys.py b/bpython/keys.py index 8956ba648..cfcac86be 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -20,16 +20,18 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - import string +from typing import TypeVar, Generic, Tuple, Dict + +T = TypeVar("T") -class KeyMap: - def __init__(self, default=""): - self.map = {} +class KeyMap(Generic[T]): + def __init__(self, default: T) -> None: + self.map: Dict[str, T] = {} self.default = default - def __getitem__(self, key): + def __getitem__(self, key: str) -> T: if not key: # Unbound key return self.default @@ -40,14 +42,14 @@ def __getitem__(self, key): f"Configured keymap ({key}) does not exist in bpython.keys" ) - def __delitem__(self, key): + def __delitem__(self, key: str): del self.map[key] - def __setitem__(self, key, value): + def __setitem__(self, key: str, value: T): self.map[key] = value -cli_key_dispatch = KeyMap(tuple()) +cli_key_dispatch: KeyMap[Tuple[str, ...]] = KeyMap(tuple()) urwid_key_dispatch = KeyMap("") # fill dispatch with letters From f8d38892258dcbed61450e9006f4937b6567dcc3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Mar 2021 22:34:25 +0200 Subject: [PATCH 1241/1650] Add type annotations --- bpython/line.py | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 8d62d7b01..14a2cf8d7 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -6,6 +6,7 @@ from itertools import chain from collections import namedtuple +from typing import Optional from .lazyre import LazyReCompile @@ -14,7 +15,7 @@ current_word_re = LazyReCompile(r"(? Optional[LinePart]: """the object.attribute.attribute just before or under the cursor""" pos = cursor_offset start = pos @@ -33,7 +34,7 @@ def current_word(cursor_offset, line): current_dict_key_re = LazyReCompile(r"""[\w_][\w0-9._]*\[([\w0-9._(), '"]*)""") -def current_dict_key(cursor_offset, line): +def current_dict_key(cursor_offset: int, line: str) -> Optional[LinePart]: """If in dictionary completion, return the current key""" for m in current_dict_key_re.finditer(line): if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: @@ -44,7 +45,7 @@ def current_dict_key(cursor_offset, line): current_dict_re = LazyReCompile(r"""([\w_][\w0-9._]*)\[([\w0-9._(), '"]*)""") -def current_dict(cursor_offset, line): +def current_dict(cursor_offset: int, line: str) -> Optional[LinePart]: """If in dictionary completion, return the dict that should be used""" for m in current_dict_re.finditer(line): if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: @@ -58,7 +59,7 @@ def current_dict(cursor_offset, line): ) -def current_string(cursor_offset, line): +def current_string(cursor_offset: int, line: str) -> Optional[LinePart]: """If inside a string of nonzero length, return the string (excluding quotes) @@ -74,7 +75,7 @@ def current_string(cursor_offset, line): current_object_re = LazyReCompile(r"([\w_][\w0-9_]*)[.]") -def current_object(cursor_offset, line): +def current_object(cursor_offset: int, line: str) -> Optional[LinePart]: """If in attribute completion, the object on which attribute should be looked up.""" match = current_word(cursor_offset, line) @@ -95,7 +96,9 @@ def current_object(cursor_offset, line): current_object_attribute_re = LazyReCompile(r"([\w_][\w0-9_]*)[.]?") -def current_object_attribute(cursor_offset, line): +def current_object_attribute( + cursor_offset: int, line: str +) -> Optional[LinePart]: """If in attribute completion, the attribute being completed""" # TODO replace with more general current_expression_attribute match = current_word(cursor_offset, line) @@ -118,7 +121,9 @@ def current_object_attribute(cursor_offset, line): ) -def current_from_import_from(cursor_offset, line): +def current_from_import_from( + cursor_offset: int, line: str +) -> Optional[LinePart]: """If in from import completion, the word after from returns None if cursor not in or just after one of the two interesting @@ -138,7 +143,9 @@ def current_from_import_from(cursor_offset, line): current_from_import_import_re_3 = LazyReCompile(r", *([\w0-9_]*)") -def current_from_import_import(cursor_offset, line): +def current_from_import_import( + cursor_offset: int, line: str +) -> Optional[LinePart]: """If in from import completion, the word after import being completed returns None if cursor not in or just after one of these words @@ -165,7 +172,7 @@ def current_from_import_import(cursor_offset, line): current_import_re_3 = LazyReCompile(r"[,][ ]*([\w0-9_.]*)") -def current_import(cursor_offset, line): +def current_import(cursor_offset: int, line: str) -> Optional[LinePart]: # TODO allow for multiple as's baseline = current_import_re_1.search(line) if baseline is None: @@ -180,12 +187,15 @@ def current_import(cursor_offset, line): end = baseline.end() + m.end(1) if start < cursor_offset and end >= cursor_offset: return LinePart(start, end, m.group(1)) + return None current_method_definition_name_re = LazyReCompile(r"def\s+([a-zA-Z_][\w]*)") -def current_method_definition_name(cursor_offset, line): +def current_method_definition_name( + cursor_offset: int, line: str +) -> Optional[LinePart]: """The name of a method being defined""" for m in current_method_definition_name_re.finditer(line): if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: @@ -196,7 +206,7 @@ def current_method_definition_name(cursor_offset, line): current_single_word_re = LazyReCompile(r"(? Optional[LinePart]: """the un-dotted word just before or under the cursor""" for m in current_single_word_re.finditer(line): if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: @@ -204,7 +214,9 @@ def current_single_word(cursor_offset, line): return None -def current_dotted_attribute(cursor_offset, line): +def current_dotted_attribute( + cursor_offset: int, line: str +) -> Optional[LinePart]: """The dotted attribute-object pair before the cursor""" match = current_word(cursor_offset, line) if match is None: @@ -212,6 +224,7 @@ def current_dotted_attribute(cursor_offset, line): start, end, word = match if "." in word[1:]: return LinePart(start, end, word) + return None current_expression_attribute_re = LazyReCompile( @@ -219,7 +232,9 @@ def current_dotted_attribute(cursor_offset, line): ) -def current_expression_attribute(cursor_offset, line): +def current_expression_attribute( + cursor_offset: int, line: str +) -> Optional[LinePart]: """If after a dot, the attribute being completed""" # TODO replace with more general current_expression_attribute for m in current_expression_attribute_re.finditer(line): From 79de0c65927b4b27fbb45e95485ac0aeb499ab53 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Mar 2021 22:40:14 +0200 Subject: [PATCH 1242/1650] Rename duplicated test name --- bpython/test/test_importcompletion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 1bf40e0d3..1d2b6b68a 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -48,7 +48,7 @@ def test_import(self): ) @unittest.expectedFailure - def test_import_empty(self): + def test_import_blank(self): self.assertSetEqual( self.module_gatherer.complete(7, "import "), {"zzabc", "zzabd", "zzefg"}, From 65fff8655bdc21cb13c9c769f645a690f75acacd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Mar 2021 22:40:25 +0200 Subject: [PATCH 1243/1650] Remove duplicated test --- bpython/test/test_importcompletion.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 1d2b6b68a..3016daf2c 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -126,11 +126,6 @@ def test_from_package(self): self.module_gatherer.complete(17, "from xml import d"), {"dom"} ) - def test_from_package(self): - self.assertSetEqual( - self.module_gatherer.complete(17, "from xml import d"), {"dom"} - ) - class TestAvoidSymbolicLinks(unittest.TestCase): def setUp(self): From 3ded87aad2b8ff227a495812fa633c63f53428c4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Mar 2021 22:42:10 +0200 Subject: [PATCH 1244/1650] Remove useless branch pathname is never used if is_package is False. --- bpython/importcompletion.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index f298efdbd..b59c17c81 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -185,8 +185,6 @@ def find_modules(self, path): if spec.submodule_search_locations is not None: pathname = spec.submodule_search_locations[0] is_package = True - else: - pathname = spec.origin except (ImportError, OSError, SyntaxError): continue except UnicodeEncodeError: From 2a3cde4ec747c9be1d9666389f93917b9d5df214 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Mar 2021 22:45:36 +0200 Subject: [PATCH 1245/1650] Add type annotations --- bpython/importcompletion.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index b59c17c81..039335351 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -26,6 +26,7 @@ import sys import warnings from pathlib import Path +from typing import Optional, Set, Generator, Tuple, List from .line import ( current_word, @@ -48,17 +49,17 @@ class ModuleGatherer: - def __init__(self, path=None, skiplist=None): + def __init__(self, path: Optional[Path] = None, skiplist=None) -> None: # The cached list of all known modules - self.modules = set() + self.modules: Set[str] = set() # List of (st_dev, st_ino) to compare against so that paths are not repeated - self.paths = set() + self.paths: Set[Tuple[int, int]] = set() # Patterns to skip self.skiplist = skiplist if skiplist is not None else tuple() self.fully_loaded = False self.find_iterator = self.find_all_modules(path) - def module_matches(self, cw, prefix=""): + def module_matches(self, cw: str, prefix: str = "") -> Set[str]: """Modules names to replace cw with""" full = f"{prefix}.{cw}" if prefix else cw @@ -72,7 +73,9 @@ def module_matches(self, cw, prefix=""): else: return set(matches) - def attr_matches(self, cw, prefix="", only_modules=False): + def attr_matches( + self, cw: str, prefix: str = "", only_modules: bool = False + ) -> Set[str]: """Attributes to replace name with""" full = f"{prefix}.{cw}" if prefix else cw module_name, _, name_after_dot = full.rpartition(".") @@ -96,11 +99,11 @@ def attr_matches(self, cw, prefix="", only_modules=False): return matches - def module_attr_matches(self, name): + def module_attr_matches(self, name: str) -> Set[str]: """Only attributes which are modules to replace name with""" return self.attr_matches(name, prefix="", only_modules=True) - def complete(self, cursor_offset, line): + def complete(self, cursor_offset: int, line: str) -> Optional[Set[str]]: """Construct a full list of possibly completions for imports.""" tokens = line.split() if "from" not in tokens and "import" not in tokens: @@ -136,7 +139,7 @@ def complete(self, cursor_offset, line): else: return None - def find_modules(self, path): + def find_modules(self, path: Path) -> Generator[str, None, None]: """Find all modules (and packages) for a given directory.""" if not path.is_dir(): # Perhaps a zip file @@ -154,7 +157,7 @@ def find_modules(self, path): # Path is not readable return - finder = importlib.machinery.FileFinder(str(path), *LOADERS) + finder = importlib.machinery.FileFinder(str(path), *LOADERS) # type: ignore for p in children: if any(fnmatch.fnmatch(p.name, entry) for entry in self.skiplist): # Path is on skiplist From 8b728d9b3c078fffb21bb014ae68b89bbc953cd3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 7 Apr 2021 21:23:09 +0200 Subject: [PATCH 1246/1650] Be verbose about Python versions --- bpython/simpleeval.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index ca8f5d51b..d0d1a9d6f 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -23,10 +23,8 @@ """simple evaluation of side-effect free code In order to provide fancy completion, some code can be executed safely. - """ - import ast import sys import builtins @@ -34,9 +32,12 @@ from . import line as line_properties from .inspection import getattr_safe +_is_py38 = sys.version_info[:2] >= (3, 8) +_is_py39 = sys.version_info[:2] >= (3, 9) + _string_type_nodes = (ast.Str, ast.Bytes) _numeric_types = (int, float, complex) -_name_type_nodes = (ast.Name, ast.NameConstant) +_name_type_nodes = (ast.Name,) if _is_py38 else (ast.Name, ast.NameConstant) class EvaluationError(Exception): @@ -88,9 +89,9 @@ def simple_eval(node_or_string, namespace=None): def _convert(node): if isinstance(node, ast.Constant): return node.value - elif isinstance(node, _string_type_nodes): + elif not _is_py38 and isinstance(node, _string_type_nodes): return node.s - elif isinstance(node, ast.Num): + elif not _is_py38 and isinstance(node, ast.Num): return node.n elif isinstance(node, ast.Tuple): return tuple(map(_convert, node.elts)) @@ -149,14 +150,16 @@ def _convert(node): return left - right # this is a deviation from literal_eval: we allow indexing - elif isinstance(node, ast.Subscript) and isinstance( - node.slice, ast.Index + elif ( + not _is_py39 + and isinstance(node, ast.Subscript) + and isinstance(node.slice, ast.Index) ): obj = _convert(node.value) index = _convert(node.slice.value) return safe_getitem(obj, index) elif ( - sys.version_info[:2] >= (3, 9) + _is_py39 and isinstance(node, ast.Subscript) and isinstance(node.slice, (ast.Constant, ast.Name)) ): From bc1916b91d496f41b72e290e01d7d8cf563674e9 Mon Sep 17 00:00:00 2001 From: oscar Date: Sat, 17 Apr 2021 14:24:48 +0200 Subject: [PATCH 1247/1650] fixes #841 --- bpython/args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/args.py b/bpython/args.py index 0a60c86ee..429ffff45 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -202,7 +202,7 @@ def exec_code(interpreter, args): spec = importlib.util.spec_from_loader("__console__", loader=None) mod = importlib.util.module_from_spec(spec) sys.modules["__console__"] = mod - interpreter.locals = mod.__dict__ + interpreter.locals.update(mod.__dict__) interpreter.locals["__file__"] = args[0] interpreter.runsource(source, args[0], "exec") sys.argv = old_argv From 2bb62657f9769e301d01f98c53fe929500c85113 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 11 May 2021 23:58:10 +0200 Subject: [PATCH 1248/1650] Add type hints --- bpython/curtsiesfrontend/events.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index fc0ef53c4..26f105dc9 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -1,5 +1,7 @@ """Non-keyboard events used in bpython curtsies REPL""" + import time +from typing import Sequence import curtsies.events @@ -7,17 +9,17 @@ class ReloadEvent(curtsies.events.Event): """Request to rerun REPL session ASAP because imported modules changed""" - def __init__(self, files_modified=("?",)): + def __init__(self, files_modified: Sequence[str] = ("?",)) -> None: self.files_modified = files_modified - def __repr__(self): - return "" % (" & ".join(self.files_modified)) + def __repr__(self) -> str: + return "".format(" & ".join(self.files_modified)) class RefreshRequestEvent(curtsies.events.Event): """Request to refresh REPL display ASAP""" - def __repr__(self): + def __repr__(self) -> str: return "" @@ -27,11 +29,11 @@ class ScheduledRefreshRequestEvent(curtsies.events.ScheduledEvent): Used to schedule the disappearance of status bar message that only shows for a few seconds""" - def __init__(self, when): + def __init__(self, when: float) -> None: super().__init__(when) - def __repr__(self): - return "" % ( + def __repr__(self) -> str: + return "".format( self.when - time.time() ) @@ -43,5 +45,5 @@ class RunStartupFileEvent(curtsies.events.Event): class UndoEvent(curtsies.events.Event): """Request to undo.""" - def __init__(self, n=1): + def __init__(self, n: int = 1) -> None: self.n = n From 6e402e2cb996946e1f77a2962a45d77e64991e05 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 11 May 2021 23:59:38 +0200 Subject: [PATCH 1249/1650] Add strings that cause wcwidth to return -1 to assertion message --- bpython/curtsiesfrontend/repl.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2a8c499cb..bd355943e 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1579,13 +1579,13 @@ def move_screen_up(current_line_start_row): assert cursor_row >= 0 and cursor_column >= 0, ( cursor_row, cursor_column, + self.current_stdouterr_line, + self.stdin.current_line, ) elif self.coderunner.running: # TODO does this ever happen? cursor_row, cursor_column = divmod( - ( - len(self.current_cursor_line_without_suggestion) - + self.cursor_offset - ), + len(self.current_cursor_line_without_suggestion) + + self.cursor_offset, width, ) assert cursor_row >= 0 and cursor_column >= 0, ( @@ -1597,19 +1597,17 @@ def move_screen_up(current_line_start_row): ) else: # Common case for determining cursor position cursor_row, cursor_column = divmod( - ( - wcswidth(self.current_cursor_line_without_suggestion.s) - - wcswidth(self.current_line) - + wcswidth(self.current_line, max(0, self.cursor_offset)) - ) + wcswidth(self.current_cursor_line_without_suggestion.s) + - wcswidth(self.current_line) + + wcswidth(self.current_line, max(0, self.cursor_offset)) + self.number_of_padding_chars_on_current_cursor_line(), width, ) assert cursor_row >= 0 and cursor_column >= 0, ( cursor_row, cursor_column, - len(self.current_cursor_line), - len(self.current_line), + self.current_cursor_line_without_suggestion.s, + self.current_line, self.cursor_offset, ) cursor_row += current_line_start_row From 37d5fddf31161d61b2f6ecee9fdef0be2110f3e8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 12 May 2021 21:33:13 +0200 Subject: [PATCH 1250/1650] Prepend element instead of appending and reversing --- bpython/history.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/history.py b/bpython/history.py index 4779eb456..1eef7d546 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -23,7 +23,7 @@ import os import stat -from itertools import islice +from itertools import islice, chain from typing import Iterable, Optional, List, TextIO from .translations import _ @@ -100,7 +100,7 @@ def entry(self) -> str: @property def entries_by_index(self) -> List[str]: - return list(reversed(self.entries + [self.saved_line])) + return list(chain((self.saved_line, ), reversed(self.entries))) def find_match_backward( self, search_term: str, include_current: bool = False From 3193adfd39b1b16ea0a020cc53c225f335f3af7e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 12 May 2021 21:45:44 +0200 Subject: [PATCH 1251/1650] Replace compiled_regex with cached_property --- bpython/lazyre.py | 35 +++++++++++++++-------------------- requirements.txt | 3 ++- setup.cfg | 7 ++++--- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 67a9de3dd..03708f3d4 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -1,6 +1,6 @@ # The MIT License # -# Copyright (c) 2015 Sebastian Ramacher +# Copyright (c) 2015-2021 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -20,8 +20,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import functools import re +from typing import Optional, Iterator + +try: + from functools import cached_property +except ImportError: + from backports.cached_property import cached_property # type: ignore class LazyReCompile: @@ -30,32 +35,22 @@ class LazyReCompile: This class allows one to store regular expressions and compiles them on first use.""" - def __init__(self, regex, flags=0): + def __init__(self, regex: str, flags: int = 0) -> None: self.regex = regex self.flags = flags - self.compiled = None - - def compile_regex(method): - @functools.wraps(method) - def _impl(self, *args, **kwargs): - if self.compiled is None: - self.compiled = re.compile(self.regex, self.flags) - return method(self, *args, **kwargs) - return _impl + @cached_property + def compiled(self) -> re.Pattern: + return re.compile(self.regex, self.flags) - @compile_regex - def finditer(self, *args, **kwargs): + def finditer(self, *args, **kwargs) -> Iterator[re.Match]: return self.compiled.finditer(*args, **kwargs) - @compile_regex - def search(self, *args, **kwargs): + def search(self, *args, **kwargs) -> Optional[re.Match]: return self.compiled.search(*args, **kwargs) - @compile_regex - def match(self, *args, **kwargs): + def match(self, *args, **kwargs) -> Optional[re.Match]: return self.compiled.match(*args, **kwargs) - @compile_regex - def sub(self, *args, **kwargs): + def sub(self, *args, **kwargs) -> str: return self.compiled.sub(*args, **kwargs) diff --git a/requirements.txt b/requirements.txt index bcdb090be..41127a240 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ Pygments +backports.cached-property; python_version < "3.9" curtsies >=0.3.5 cwcwidth greenlet pyxdg requests -setuptools +setuptools \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 8b6177591..afde695ad 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,12 +20,13 @@ packages = bpython.translations bpdb install_requires = - pygments - requests + backports.cached-property; python_version < "3.9" curtsies >=0.3.5 - greenlet cwcwidth + greenlet + pygments pyxdg + requests [options.extras_require] clipboard = pyperclip From 84a54df6a28c2d0335be5fe96e3f6ba837806462 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 12 May 2021 22:02:54 +0200 Subject: [PATCH 1252/1650] Python 3.6 does not yet know about re.Pattern --- bpython/lazyre.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 03708f3d4..536275326 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -40,7 +40,7 @@ def __init__(self, regex: str, flags: int = 0) -> None: self.flags = flags @cached_property - def compiled(self) -> re.Pattern: + def compiled(self): return re.compile(self.regex, self.flags) def finditer(self, *args, **kwargs) -> Iterator[re.Match]: From 55a2b5a46ef30f1b3a184dd7ae3a7fe7b8638ff8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 12 May 2021 22:03:09 +0200 Subject: [PATCH 1253/1650] Apply black --- bpython/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/history.py b/bpython/history.py index 1eef7d546..dfbab2ada 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -100,7 +100,7 @@ def entry(self) -> str: @property def entries_by_index(self) -> List[str]: - return list(chain((self.saved_line, ), reversed(self.entries))) + return list(chain((self.saved_line,), reversed(self.entries))) def find_match_backward( self, search_term: str, include_current: bool = False From bf2b32f8bab148b6c8690c4bed94f435f387f4b3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 12 May 2021 22:06:35 +0200 Subject: [PATCH 1254/1650] Apply black --- bpdb/debugger.py | 2 +- bpython/curtsiesfrontend/interaction.py | 2 +- bpython/simpleeval.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bpdb/debugger.py b/bpdb/debugger.py index d575b2671..b98e9612a 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -25,7 +25,7 @@ class BPdb(pdb.Pdb): - """ PDB with BPython support. """ + """PDB with BPython support.""" def __init__(self, *args, **kwargs): pdb.Pdb.__init__(self, *args, **kwargs) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index dd9b13a92..ac92e3fb0 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -167,7 +167,7 @@ def confirm(self, q): return self.main_context.switch(q) def file_prompt(self, s): - """Expected to return a file name, given """ + """Expected to return a file name, given""" self.request_context = greenlet.getcurrent() self.prompt = s self.in_prompt = True diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index d0d1a9d6f..801da318a 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -179,7 +179,7 @@ def _convert(node): def safe_getitem(obj, index): - """ Safely tries to access obj[index] """ + """Safely tries to access obj[index]""" if type(obj) in (list, tuple, dict, bytes, str): try: return obj[index] From 3237223a69fb2acbd526ebf1e7ebed56c39e670c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 12 May 2021 22:11:52 +0200 Subject: [PATCH 1255/1650] Remove more type annotations that do not work with Python 3.6 --- bpython/lazyre.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 536275326..fbbdd38d8 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -43,13 +43,13 @@ def __init__(self, regex: str, flags: int = 0) -> None: def compiled(self): return re.compile(self.regex, self.flags) - def finditer(self, *args, **kwargs) -> Iterator[re.Match]: + def finditer(self, *args, **kwargs): return self.compiled.finditer(*args, **kwargs) - def search(self, *args, **kwargs) -> Optional[re.Match]: + def search(self, *args, **kwargs): return self.compiled.search(*args, **kwargs) - def match(self, *args, **kwargs) -> Optional[re.Match]: + def match(self, *args, **kwargs): return self.compiled.match(*args, **kwargs) def sub(self, *args, **kwargs) -> str: From 5de943b4ec832596ee5464662aa956a7961775bf Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 12 May 2021 22:30:49 +0200 Subject: [PATCH 1256/1650] Record relevant versions in log output --- bpython/args.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/bpython/args.py b/bpython/args.py index 429ffff45..25a8d95f7 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -1,7 +1,7 @@ # The MIT License # # Copyright (c) 2008 Bob Farrell -# Copyright (c) 2012-2020 Sebastian Ramacher +# Copyright (c) 2012-2021 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -26,9 +26,14 @@ """ import argparse +import curtsies +import cwcwidth +import greenlet import importlib.util import logging import os +import pygments +import requests import sys from pathlib import Path @@ -36,6 +41,8 @@ from .config import default_config_path, loadini, Struct from .translations import _ +logger = logging.getLogger(__name__) + class ArgumentParserFailed(ValueError): """Raised by the RaisingOptionParser for a bogus commandline.""" @@ -184,6 +191,25 @@ def callback(group): bpython_logger.addHandler(logging.NullHandler()) curtsies_logger.addHandler(logging.NullHandler()) + logger.info(f"Starting bpython {__version__}") + logger.info(f"Python {sys.executable}: {sys.version_info}") + logger.info(f"curtsies: {curtsies.__version__}") + logger.info(f"cwcwidth: {cwcwidth.__version__}") + logger.info(f"greenlet: {greenlet.__version__}") + logger.info(f"pygments: {pygments.__version__}") + logger.info(f"requests: {requests.__version__}") + logger.info( + "environment:\n{}".format( + "\n".join( + f"{key}: {value}" + for key, value in sorted(os.environ.items()) + if key.startswith("LC") + or key.startswith("LANG") + or key == "TERM" + ) + ) + ) + config = Struct() loadini(config, options.config) From da150e4284d464d5b6e2e6dd605c02d16f0876b1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 12 May 2021 23:30:30 +0200 Subject: [PATCH 1257/1650] Implement more efficient __contains__ --- bpython/curtsiesfrontend/manual_readline.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 15aea35c2..b8e334e8a 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -22,13 +22,6 @@ class AbstractEdits: "cut_buffer": "there", } - def __contains__(self, key): - try: - self[key] - except KeyError: - return False - else: - return True def add(self, key, func, overwrite=False): if key in self: @@ -75,6 +68,9 @@ def call_without_cut(self, key, **kwargs): r = self.call_for_two(key, **kwargs) return r[:2] + def __contains__(self, key): + return key in self.simple_edits or key in self.cut_buffer_edits + def __getitem__(self, key): if key in self.simple_edits: return self.simple_edits[key] From c0a783752f70fb0157c7d3cd7554b95e941c4372 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 12 May 2021 23:30:51 +0200 Subject: [PATCH 1258/1650] Fix function signature --- bpython/curtsiesfrontend/manual_readline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index b8e334e8a..48f96b097 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -156,7 +156,7 @@ def __init__( def add_config_attr(self, config_attr, func): raise NotImplementedError("Config already set on this mapping") - def add(self, key, func): + def add(self, key, func, overwrite=False): raise NotImplementedError("Config already set on this mapping") From 517ae805c3f5f99990267477a424612551b38625 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 12 May 2021 23:31:11 +0200 Subject: [PATCH 1259/1650] Initialize attributes in base class --- bpython/curtsiesfrontend/manual_readline.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 48f96b097..afeb55531 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -22,6 +22,12 @@ class AbstractEdits: "cut_buffer": "there", } + def __init__(self, simple_edits=None, cut_buffer_edits=None): + self.simple_edits = {} if simple_edits is None else simple_edits + self.cut_buffer_edits = ( + {} if cut_buffer_edits is None else cut_buffer_edits + ) + self.awaiting_config = {} def add(self, key, func, overwrite=False): if key in self: @@ -104,11 +110,6 @@ class UnconfiguredEdits(AbstractEdits): Keys can't be added twice, config attributes can't be added twice. """ - def __init__(self): - self.simple_edits = {} - self.cut_buffer_edits = {} - self.awaiting_config = {} - def mapping_with_config(self, config, key_dispatch): """Creates a new mapping object by applying a config object""" return ConfiguredEdits( @@ -147,8 +148,7 @@ def __init__( config, key_dispatch, ): - self.simple_edits = dict(simple_edits) - self.cut_buffer_edits = dict(cut_buffer_edits) + super().__init__(dict(simple_edits), dict(cut_buffer_edits)) for attr, func in awaiting_config.items(): for key in key_dispatch[getattr(config, attr)]: super().add(key, func, overwrite=True) From 6527442a2526de96f8463e381edab4fb9d80f554 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 15:13:35 +0200 Subject: [PATCH 1260/1650] Fix version constraint for backports.cached-property --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 41127a240..7f56dc0fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Pygments -backports.cached-property; python_version < "3.9" +backports.cached-property; python_version < "3.8" curtsies >=0.3.5 cwcwidth greenlet diff --git a/setup.cfg b/setup.cfg index afde695ad..7c41b4788 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,7 +20,7 @@ packages = bpython.translations bpdb install_requires = - backports.cached-property; python_version < "3.9" + backports.cached-property; python_version < "3.8" curtsies >=0.3.5 cwcwidth greenlet From dac3dd72bf339d4e8df76cd94e473c8fc43c2e93 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 15:28:19 +0200 Subject: [PATCH 1261/1650] Add type annotations --- bpython/curtsiesfrontend/parse.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 6076a027a..6a42b3764 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -23,19 +23,23 @@ INVERSE_COLORS["default"] = INVERSE_COLORS[CURTSIES_COLORS[0]] -def func_for_letter(l, default="k"): +def func_for_letter(letter_color_code: str, default: str = "k"): """Returns FmtStr constructor for a bpython-style color code""" - if l == "d": - l = default - elif l == "D": - l = default.upper() - return partial(fmtstr, fg=CNAMES[l.lower()], bold=l.isupper()) + if letter_color_code == "d": + letter_color_code = default + elif letter_color_code == "D": + letter_color_code = default.upper() + return partial( + fmtstr, + fg=CNAMES[letter_color_code.lower()], + bold=letter_color_code.isupper(), + ) -def color_for_letter(l, default="k"): - if l == "d": - l = default - return CNAMES[l.lower()] +def color_for_letter(letter_color_code: str, default: str = "k"): + if letter_color_code == "d": + letter_color_code = default + return CNAMES[letter_color_code.lower()] def parse(s): From 93b1cdc8d23cca8581080ae336c7f2c6c29d2376 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 15:40:15 +0200 Subject: [PATCH 1262/1650] Add type annotations --- bpython/config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index cdd8ca35d..3e796056a 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -47,12 +47,12 @@ class Struct: to and use for various arbitrary things.""" -def getpreferredencoding(): +def getpreferredencoding() -> str: """Get the user's preferred encoding.""" return locale.getpreferredencoding() or sys.getdefaultencoding() -def can_encode(c): +def can_encode(c: str) -> bool: try: c.encode(getpreferredencoding()) return True @@ -60,22 +60,22 @@ def can_encode(c): return False -def supports_box_chars(): +def supports_box_chars() -> bool: """Check if the encoding supports Unicode box characters.""" return all(map(can_encode, "│─└┘┌┐")) -def get_config_home(): +def get_config_home() -> Path: """Returns the base directory for bpython's configuration files.""" return Path(BaseDirectory.xdg_config_home) / "bpython" -def default_config_path(): +def default_config_path() -> Path: """Returns bpython's default configuration file path.""" return get_config_home() / "config" -def default_editor(): +def default_editor() -> str: """Returns the default editor.""" return os.environ.get("VISUAL", os.environ.get("EDITOR", "vi")) From a34f77f68fdb3f6eae7b1786fc2b62b29cdc77b1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 15:40:26 +0200 Subject: [PATCH 1263/1650] USe str instead of f-string --- bpython/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index 3e796056a..6dd471d98 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -87,7 +87,7 @@ def fill_config_with_default_values(config, default_values): for (opt, val) in default_values[section].items(): if not config.has_option(section, opt): - config.set(section, opt, f"{val}") + config.set(section, opt, str(val)) def loadini(struct, config_path): From faa65fc4c5c82e97b83891724aa3bdbde7b13b61 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 15:40:36 +0200 Subject: [PATCH 1264/1650] Extend error message --- bpython/config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 6dd471d98..411aec139 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -194,8 +194,10 @@ def loadini(struct, config_path): except UnicodeDecodeError as e: sys.stderr.write( "Error: Unable to parse config file at '{}' due to an " - "encoding issue. Please make sure to fix the encoding " - "of the file or remove it and then try again.\n".format(config_path) + "encoding issue ({}). Please make sure to fix the encoding " + "of the file or remove it and then try again.\n".format( + config_path, e + ) ) sys.exit(1) From 0acf2688fafb709ab7ca767a95a66d2f4ef7dd0c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 15:59:41 +0200 Subject: [PATCH 1265/1650] Simplify ps1/ps2 handling --- bpython/repl.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 62e008101..39388eedf 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -459,18 +459,12 @@ def __init__(self, interp, config): ) @property - def ps1(self): - try: - return sys.ps1 - except AttributeError: - return ">>> " + def ps1(self) -> str: + return getattr(sys, "ps1", ">>> ") @property - def ps2(self): - try: - return sys.ps2 - except AttributeError: - return "... " + def ps2(self) -> str: + return getattr(sys, "ps2", "... ") def startup(self): """ From 0ac6f137f28695673859d6d0b611dd0c7bc8a01d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 22:09:27 +0200 Subject: [PATCH 1266/1650] Use super --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index bd355943e..9b845420f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1833,7 +1833,7 @@ def prompt_undo(self): return self.take_back_empty_line() def prompt_for_undo(): - n = BpythonRepl.prompt_undo(self) + n = super(BaseRepl, self).prompt_undo() if n > 0: self.request_undo(n=n) From 7f4abb2de9218603a352afac7fd82740504cd617 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 22:09:43 +0200 Subject: [PATCH 1267/1650] Use list literal --- bpython/curtsiesfrontend/repl.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9b845420f..c5af8db16 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -2015,11 +2015,10 @@ def key_help_text(self): "yank from buffer", "cut to buffer", ) - pairs = [] - pairs.append( - ["complete history suggestion", "right arrow at end of line"] - ) - pairs.append(["previous match with current line", "up arrow"]) + pairs = [ + ["complete history suggestion", "right arrow at end of line"], + ["previous match with current line", "up arrow"], + ] for functionality, key in ( (attr[:-4].replace("_", " "), getattr(self.config, attr)) for attr in self.config.__dict__ From de90c373930f35fdc32b8f89459001d4fe9605be Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 22:10:16 +0200 Subject: [PATCH 1268/1650] Add type annotations --- bpython/config.py | 2 +- bpython/inspection.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 411aec139..693d59904 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -37,7 +37,7 @@ class UnknownColorCode(Exception): - def __init__(self, key, color): + def __init__(self, key: str, color: str) -> None: self.key = key self.color = color diff --git a/bpython/inspection.py b/bpython/inspection.py index 222def51c..4c0dbada4 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -29,6 +29,7 @@ from pygments.token import Token from pygments.lexers import Python3Lexer +from typing import Any from types import MemberDescriptorType from .lazyre import LazyReCompile @@ -53,7 +54,7 @@ class AttrCleaner: """A context manager that tries to make an object not exhibit side-effects on attribute lookup.""" - def __init__(self, obj): + def __init__(self, obj: Any) -> None: self.obj = obj def __enter__(self): From 159e1530ef3264bf20f79b6af396285bea32b373 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 22:28:57 +0200 Subject: [PATCH 1269/1650] Remove some renames --- bpython/curtsiesfrontend/interaction.py | 6 ++-- bpython/curtsiesfrontend/repl.py | 44 +++++++++++-------------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index ac92e3fb0..71a3ef3eb 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -3,12 +3,12 @@ import curtsies.events as events from ..translations import _ -from ..repl import Interaction as BpythonInteraction +from ..repl import Interaction from ..curtsiesfrontend.events import RefreshRequestEvent from ..curtsiesfrontend.manual_readline import edit_keys -class StatusBar(BpythonInteraction): +class StatusBar(Interaction): """StatusBar and Interaction for Repl Passing of control back and forth between calls that use interact api @@ -157,7 +157,7 @@ def notify(self, msg, n=3, wait_for_keypress=False): self.request_refresh() self.main_context.switch(msg) - # below Really ought to be called from greenlets other than main because + # below really ought to be called from greenlets other than main because # they block def confirm(self, q): """Expected to return True or False, given question prompt q""" diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c5af8db16..9ed039bef 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1,7 +1,5 @@ -import blessings import contextlib import errno -import greenlet import itertools import logging import os @@ -13,12 +11,8 @@ import time import unicodedata -from pygments import format as pygformat -from pygments.lexers import Python3Lexer -from pygments.formatters import TerminalFormatter - -from cwcwidth import wcswidth - +import blessings +import greenlet from curtsies import ( FSArray, fmtstr, @@ -30,17 +24,10 @@ ) from curtsies.configfile_keynames import keymap as key_dispatch from curtsies.input import is_main_thread - -from .. import __version__, autocomplete -from ..repl import ( - Repl as BpythonRepl, - SourceNotFound, - LineTypeTranslator as LineType, -) -from ..config import getpreferredencoding -from ..formatter import BPythonFormatter -from ..translations import _ -from ..pager import get_pager_command +from cwcwidth import wcswidth +from pygments import format as pygformat +from pygments.formatters import TerminalFormatter +from pygments.lexers import Python3Lexer from . import events as bpythonevents, sitefix, replpainter as paint from .coderunner import ( @@ -49,14 +36,23 @@ ) from .filewatch import ModuleChangedEventHandler from .interaction import StatusBar -from .manual_readline import edit_keys -from .parse import parse as bpythonparse, func_for_letter, color_for_letter -from .preprocess import preprocess from .interpreter import ( Interp, code_finished_will_parse, ) - +from .manual_readline import edit_keys +from .parse import parse as bpythonparse, func_for_letter, color_for_letter +from .preprocess import preprocess +from .. import __version__ +from ..config import getpreferredencoding +from ..formatter import BPythonFormatter +from ..pager import get_pager_command +from ..repl import ( + Repl, + SourceNotFound, + LineTypeTranslator as LineType, +) +from ..translations import _ logger = logging.getLogger(__name__) @@ -271,7 +267,7 @@ def _find_module(self, fullname, path=None): return ImportLoader(self.watcher, loader) -class BaseRepl(BpythonRepl): +class BaseRepl(Repl): """Python Repl Reacts to events like From 8bd20fc9aa96784de011b4c190cb5a19077dba6d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 22:32:57 +0200 Subject: [PATCH 1270/1650] Remove useless assignment --- bpython/curtsiesfrontend/interaction.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 71a3ef3eb..2ba225f12 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -171,5 +171,4 @@ def file_prompt(self, s): self.request_context = greenlet.getcurrent() self.prompt = s self.in_prompt = True - result = self.main_context.switch(s) - return result + return self.main_context.switch(s) From bae3fd05a44d1ca22368c25814729ed64f8104c9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 22:49:01 +0200 Subject: [PATCH 1271/1650] Override get_session_formatted_for_file in curtsies Repl --- bpython/curtsiesfrontend/repl.py | 23 ++++++++++++++-- bpython/repl.py | 45 ++++++++------------------------ 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 9ed039bef..d35317303 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -10,6 +10,7 @@ import tempfile import time import unicodedata +from enum import Enum import blessings import greenlet @@ -50,7 +51,6 @@ from ..repl import ( Repl, SourceNotFound, - LineTypeTranslator as LineType, ) from ..translations import _ @@ -70,6 +70,14 @@ MAX_EVENTS_POSSIBLY_NOT_PASTE = 20 +class LineType(Enum): + """Used when adding a tuple to all_logical_lines, to get input / output values + having to actually type/know the strings""" + + INPUT = "input" + OUTPUT = "output" + + class FakeStdin: """The stdin object user code will reference @@ -365,7 +373,7 @@ def __init__( # Entries are tuples, where # - the first element the line (string, not fmtsr) # - the second element is one of 2 global constants: "input" or "output" - # (use LineType.INPUT or LineType.OUTPUT to avoid typing these strings) + # (use LineTypeTranslator.INPUT or LineTypeTranslator.OUTPUT to avoid typing these strings) self.all_logical_lines = [] # formatted version of lines in the buffer kept around so we can @@ -2032,6 +2040,17 @@ def key_help_text(self): "{} : {}".format(func.rjust(max_func), key) for func, key in pairs ) + def get_session_formatted_for_file(self) -> str: + def process(): + for line, lineType in self.all_logical_lines: + if lineType == LineType.INPUT: + yield line + elif line.rstrip(): + yield "# OUT: %s" % line + yield "### %s" % self.current_line + + return "\n".join(process()) + def is_nop(char): return unicodedata.category(str(char)) == "Cc" diff --git a/bpython/repl.py b/bpython/repl.py index 39388eedf..c1ba03617 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -33,7 +33,6 @@ import textwrap import time import traceback -from enum import Enum from itertools import takewhile from pathlib import Path from pygments.lexers import Python3Lexer @@ -355,14 +354,6 @@ class SourceNotFound(Exception): """Exception raised when the requested source could not be found.""" -class LineTypeTranslator(Enum): - """Used when adding a tuple to all_logical_lines, to get input / output values - having to actually type/know the strings""" - - INPUT = "input" - OUTPUT = "output" - - class Repl: """Implements the necessary guff for a Python-repl-alike interface @@ -778,37 +769,23 @@ def line_is_empty(line): indentation = 0 return indentation - def get_session_formatted_for_file(self): + def get_session_formatted_for_file(self) -> str: """Format the stdout buffer to something suitable for writing to disk, i.e. without >>> and ... at input lines and with "# OUT: " prepended to output lines and "### " prepended to current line""" - if hasattr(self, "all_logical_lines"): - # Curtsies - - def process(): - for line, lineType in self.all_logical_lines: - if lineType == LineTypeTranslator.INPUT: - yield line - elif line.rstrip(): - yield "# OUT: %s" % line - yield "### %s" % self.current_line - - return "\n".join(process()) - - else: # cli and Urwid - session_output = self.getstdout() + session_output = self.getstdout() - def process(): - for line in session_output.split("\n"): - if line.startswith(self.ps1): - yield line[len(self.ps1) :] - elif line.startswith(self.ps2): - yield line[len(self.ps2) :] - elif line.rstrip(): - yield f"# OUT: {line}" + def process(): + for line in session_output.split("\n"): + if line.startswith(self.ps1): + yield line[len(self.ps1) :] + elif line.startswith(self.ps2): + yield line[len(self.ps2) :] + elif line.rstrip(): + yield f"# OUT: {line}" - return "\n".join(process()) + return "\n".join(process()) def write2file(self): """Prompt for a filename and write the current contents of the stdout From 626dcce3e9c4f13c4ff1614d0bff9ec6cb620de9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 23:08:53 +0200 Subject: [PATCH 1272/1650] Fix import --- bpython/test/test_curtsies_repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index a95990279..f7798ae6f 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -12,7 +12,7 @@ from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython.curtsiesfrontend import interpreter from bpython.curtsiesfrontend import events as bpythonevents -from bpython.repl import LineTypeTranslator as LineType +from bpython.curtsiesfrontend.repl import LineType from bpython import autocomplete from bpython import config from bpython import args From 979f2391af9fae0cc11fd135945cdf69c4af241f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 23:21:40 +0200 Subject: [PATCH 1273/1650] Fix import --- bpython/curtsiesfrontend/interaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 2ba225f12..51fd28d35 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -1,6 +1,6 @@ import greenlet import time -import curtsies.events as events +from curtsies import events from ..translations import _ from ..repl import Interaction From dd596b443f9574912ae5618a1536f5859439d0c1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 16 May 2021 23:32:47 +0200 Subject: [PATCH 1274/1650] Put history files in temporary directory --- bpython/test/test_history.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index 71d7847ce..544a644eb 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -1,5 +1,6 @@ -import os +import tempfile import unittest +from pathlib import Path from bpython.config import getpreferredencoding from bpython.history import History @@ -7,7 +8,7 @@ class TestHistory(unittest.TestCase): def setUp(self): - self.history = History("#%d" % x for x in range(1000)) + self.history = History(f"#{x}" for x in range(1000)) def test_is_at_start(self): self.history.first() @@ -84,7 +85,8 @@ def test_reset(self): class TestHistoryFileAccess(unittest.TestCase): def setUp(self): - self.filename = "history_temp_file" + self.tempdir = tempfile.TemporaryDirectory() + self.filename = str(Path(self.tempdir.name) / "history_temp_file") self.encoding = getpreferredencoding() with open( @@ -109,21 +111,17 @@ def test_append_reload_and_write(self): def test_save(self): history = History() - history.entries = [] - for line in ["#1", "#2", "#3", "#4"]: + for line in ("#1", "#2", "#3", "#4"): history.append_to(history.entries, line) # save only last 2 lines history.save(self.filename, self.encoding, lines=2) - # empty the list of entries and load again from the file - history.entries = [""] + # load again from the file + history = History() history.load(self.filename, self.encoding) self.assertEqual(history.entries, ["#3", "#4"]) def tearDown(self): - try: - os.remove(self.filename) - except OSError: - pass + self.tempdir = None From 2240883457342242f3b7f08699e913ece7471ef6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 20 May 2021 13:37:11 +0200 Subject: [PATCH 1275/1650] Move to OFTC channel --- doc/sphinx/source/community.rst | 4 ++-- doc/sphinx/source/contributing.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/sphinx/source/community.rst b/doc/sphinx/source/community.rst index 00b8c4093..911bbc4f6 100644 --- a/doc/sphinx/source/community.rst +++ b/doc/sphinx/source/community.rst @@ -10,8 +10,8 @@ These are the places where you can find us. IRC --- -You can find us in `#bpython `_ on the `Freenode -`_ network. Don't worry when you get no response (this does +You can find us in `#bpython `_ on the `OFTC +`_ network. Don't worry when you get no response (this does not usually happen) but we are all from Europe and when you get to the channel during our nighttime you might have to wait a while for a response. diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index ade107476..84fb2da55 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -10,7 +10,7 @@ these are particularly good ones to start out with. See our section about the :ref:`community` for a list of resources. -`#bpython `_ on Freenode is particularly useful, +`#bpython `_ on OFTC is particularly useful, but you might have to wait for a while to get a question answered depending on the time of day. From 81eb0a5fff60cdb15cefb46b18889fdc1dc51a6b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 18 May 2021 00:07:37 +0200 Subject: [PATCH 1276/1650] Replace loadini with a Config object --- bpython/args.py | 7 +- bpython/config.py | 551 +++++++++++++------------ bpython/test/test_config.py | 22 +- bpython/test/test_curtsies_painting.py | 3 +- bpython/test/test_curtsies_repl.py | 3 +- bpython/test/test_repl.py | 3 +- doc/sphinx/source/simplerepl.py | 5 +- 7 files changed, 293 insertions(+), 301 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 25a8d95f7..e3095e17c 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -38,7 +38,7 @@ from pathlib import Path from . import __version__, __copyright__ -from .config import default_config_path, loadini, Struct +from .config import default_config_path, Config from .translations import _ logger = logging.getLogger(__name__) @@ -210,10 +210,7 @@ def callback(group): ) ) - config = Struct() - loadini(config, options.config) - - return config, options, options.args + return Config(options.config), options, options.args def exec_code(interpreter, args): diff --git a/bpython/config.py b/bpython/config.py index 693d59904..399782de2 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -21,13 +21,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - import os import sys import locale from configparser import ConfigParser from itertools import chain from pathlib import Path +from typing import MutableMapping, Mapping, Any, Dict from xdg import BaseDirectory from .autocomplete import AutocompleteModes @@ -90,284 +90,291 @@ def fill_config_with_default_values(config, default_values): config.set(section, opt, str(val)) -def loadini(struct, config_path): - """Loads .ini configuration file and stores its values in struct""" - - config = ConfigParser() - defaults = { - "general": { - "arg_spec": True, - "auto_display_list": True, - "autocomplete_mode": default_completion, - "color_scheme": "default", - "complete_magic_methods": True, - "dedent_after": 1, - "default_autoreload": False, - "editor": default_editor(), - "flush_output": True, - "import_completion_skiplist": ":".join( - ( - # version tracking - ".git", - ".svn", - ".hg" - # XDG - ".config", - ".local", - ".share", - # nodejs - "node_modules", - # PlayOnLinux - "PlayOnLinux's virtual drives", - # wine - "dosdevices", - # Python byte code cache - "__pycache__", +class Config: + def __init__(self, config_path: Path): + """Loads .ini configuration file and stores its values.""" + + config = ConfigParser() + defaults: Dict[str, Dict[str, Any]] = { + "general": { + "arg_spec": True, + "auto_display_list": True, + "autocomplete_mode": default_completion, + "color_scheme": "default", + "complete_magic_methods": True, + "dedent_after": 1, + "default_autoreload": False, + "editor": default_editor(), + "flush_output": True, + "import_completion_skiplist": ":".join( + ( + # version tracking + ".git", + ".svn", + ".hg" + # XDG + ".config", + ".local", + ".share", + # nodejs + "node_modules", + # PlayOnLinux + "PlayOnLinux's virtual drives", + # wine + "dosdevices", + # Python byte code cache + "__pycache__", + ) + ), + "highlight_show_source": True, + "hist_duplicates": True, + "hist_file": "~/.pythonhist", + "hist_length": 1000, + "paste_time": 0.02, + "pastebin_confirm": True, + "pastebin_expiry": "1week", + "pastebin_helper": "", + "pastebin_url": "https://bpaste.net", + "save_append_py": False, + "single_undo_time": 1.0, + "syntax": True, + "tab_length": 4, + "unicode_box": True, + }, + "keyboard": { + "backspace": "C-h", + "beginning_of_line": "C-a", + "clear_line": "C-u", + "clear_screen": "C-l", + "clear_word": "C-w", + "copy_clipboard": "F10", + "cut_to_buffer": "C-k", + "delete": "C-d", + "down_one_line": "C-n", + "edit_config": "F3", + "edit_current_block": "C-x", + "end_of_line": "C-e", + "exit": "", + "external_editor": "F7", + "help": "F1", + "incremental_search": "M-s", + "last_output": "F9", + "left": "C-b", + "pastebin": "F8", + "redo": "C-g", + "reimport": "F6", + "reverse_incremental_search": "M-r", + "right": "C-f", + "save": "C-s", + "search": "C-o", + "show_source": "F2", + "suspend": "C-z", + "toggle_file_watch": "F5", + "transpose_chars": "C-t", + "undo": "C-r", + "up_one_line": "C-p", + "yank_from_buffer": "C-y", + }, + "cli": { + "suggestion_width": 0.8, + "trim_prompts": False, + }, + "curtsies": { + "list_above": False, + "right_arrow_completion": True, + }, + } + + default_keys_to_commands = { + value: key for (key, value) in defaults["keyboard"].items() + } + + fill_config_with_default_values(config, defaults) + try: + config.read(config_path) + except UnicodeDecodeError as e: + sys.stderr.write( + "Error: Unable to parse config file at '{}' due to an " + "encoding issue ({}). Please make sure to fix the encoding " + "of the file or remove it and then try again.\n".format( + config_path, e ) - ), - "highlight_show_source": True, - "hist_duplicates": True, - "hist_file": "~/.pythonhist", - "hist_length": 1000, - "paste_time": 0.02, - "pastebin_confirm": True, - "pastebin_expiry": "1week", - "pastebin_helper": "", - "pastebin_url": "https://bpaste.net", - "save_append_py": False, - "single_undo_time": 1.0, - "syntax": True, - "tab_length": 4, - "unicode_box": True, - }, - "keyboard": { - "backspace": "C-h", - "beginning_of_line": "C-a", - "clear_line": "C-u", - "clear_screen": "C-l", - "clear_word": "C-w", - "copy_clipboard": "F10", - "cut_to_buffer": "C-k", - "delete": "C-d", - "down_one_line": "C-n", - "edit_config": "F3", - "edit_current_block": "C-x", - "end_of_line": "C-e", - "exit": "", - "external_editor": "F7", - "help": "F1", - "incremental_search": "M-s", - "last_output": "F9", - "left": "C-b", - "pastebin": "F8", - "redo": "C-g", - "reimport": "F6", - "reverse_incremental_search": "M-r", - "right": "C-f", - "save": "C-s", - "search": "C-o", - "show_source": "F2", - "suspend": "C-z", - "toggle_file_watch": "F5", - "transpose_chars": "C-t", - "undo": "C-r", - "up_one_line": "C-p", - "yank_from_buffer": "C-y", - }, - "cli": { - "suggestion_width": 0.8, - "trim_prompts": False, - }, - "curtsies": { - "list_above": False, - "right_arrow_completion": True, - }, - } - - default_keys_to_commands = { - value: key for (key, value) in defaults["keyboard"].items() - } - - fill_config_with_default_values(config, defaults) - try: - config.read(config_path) - except UnicodeDecodeError as e: - sys.stderr.write( - "Error: Unable to parse config file at '{}' due to an " - "encoding issue ({}). Please make sure to fix the encoding " - "of the file or remove it and then try again.\n".format( - config_path, e ) + sys.exit(1) + + def get_key_no_doublebind(command): + default_commands_to_keys = defaults["keyboard"] + requested_key = config.get("keyboard", command) + + try: + default_command = default_keys_to_commands[requested_key] + + if default_commands_to_keys[default_command] == config.get( + "keyboard", default_command + ): + setattr(self, "%s_key" % default_command, "") + except KeyError: + pass + + return requested_key + + self.config_path = Path(config_path).absolute() + self.hist_file = Path(config.get("general", "hist_file")).expanduser() + + self.dedent_after = config.getint("general", "dedent_after") + self.tab_length = config.getint("general", "tab_length") + self.auto_display_list = config.getboolean( + "general", "auto_display_list" + ) + self.syntax = config.getboolean("general", "syntax") + self.arg_spec = config.getboolean("general", "arg_spec") + self.paste_time = config.getfloat("general", "paste_time") + self.single_undo_time = config.getfloat("general", "single_undo_time") + self.highlight_show_source = config.getboolean( + "general", "highlight_show_source" + ) + self.editor = config.get("general", "editor") + self.hist_length = config.getint("general", "hist_length") + self.hist_duplicates = config.getboolean("general", "hist_duplicates") + self.flush_output = config.getboolean("general", "flush_output") + self.default_autoreload = config.getboolean( + "general", "default_autoreload" + ) + self.import_completion_skiplist = config.get( + "general", "import_completion_skiplist" + ).split(":") + + self.pastebin_key = get_key_no_doublebind("pastebin") + self.copy_clipboard_key = get_key_no_doublebind("copy_clipboard") + self.save_key = get_key_no_doublebind("save") + self.search_key = get_key_no_doublebind("search") + self.show_source_key = get_key_no_doublebind("show_source") + self.suspend_key = get_key_no_doublebind("suspend") + self.toggle_file_watch_key = get_key_no_doublebind("toggle_file_watch") + self.undo_key = get_key_no_doublebind("undo") + self.redo_key = get_key_no_doublebind("redo") + self.reimport_key = get_key_no_doublebind("reimport") + self.reverse_incremental_search_key = get_key_no_doublebind( + "reverse_incremental_search" + ) + self.incremental_search_key = get_key_no_doublebind( + "incremental_search" + ) + self.up_one_line_key = get_key_no_doublebind("up_one_line") + self.down_one_line_key = get_key_no_doublebind("down_one_line") + self.cut_to_buffer_key = get_key_no_doublebind("cut_to_buffer") + self.yank_from_buffer_key = get_key_no_doublebind("yank_from_buffer") + self.clear_word_key = get_key_no_doublebind("clear_word") + self.backspace_key = get_key_no_doublebind("backspace") + self.clear_line_key = get_key_no_doublebind("clear_line") + self.clear_screen_key = get_key_no_doublebind("clear_screen") + self.delete_key = get_key_no_doublebind("delete") + + self.left_key = get_key_no_doublebind("left") + self.right_key = get_key_no_doublebind("right") + self.end_of_line_key = get_key_no_doublebind("end_of_line") + self.beginning_of_line_key = get_key_no_doublebind("beginning_of_line") + self.transpose_chars_key = get_key_no_doublebind("transpose_chars") + self.exit_key = get_key_no_doublebind("exit") + self.last_output_key = get_key_no_doublebind("last_output") + self.edit_config_key = get_key_no_doublebind("edit_config") + self.edit_current_block_key = get_key_no_doublebind( + "edit_current_block" ) - sys.exit(1) + self.external_editor_key = get_key_no_doublebind("external_editor") + self.help_key = get_key_no_doublebind("help") - def get_key_no_doublebind(command): - default_commands_to_keys = defaults["keyboard"] - requested_key = config.get("keyboard", command) + self.pastebin_confirm = config.getboolean("general", "pastebin_confirm") + self.pastebin_url = config.get("general", "pastebin_url") + self.pastebin_expiry = config.get("general", "pastebin_expiry") + self.pastebin_helper = config.get("general", "pastebin_helper") - try: - default_command = default_keys_to_commands[requested_key] - - if default_commands_to_keys[default_command] == config.get( - "keyboard", default_command - ): - setattr(struct, "%s_key" % default_command, "") - except KeyError: - pass - - return requested_key - - struct.config_path = Path(config_path).absolute() - - struct.dedent_after = config.getint("general", "dedent_after") - struct.tab_length = config.getint("general", "tab_length") - struct.auto_display_list = config.getboolean("general", "auto_display_list") - struct.syntax = config.getboolean("general", "syntax") - struct.arg_spec = config.getboolean("general", "arg_spec") - struct.paste_time = config.getfloat("general", "paste_time") - struct.single_undo_time = config.getfloat("general", "single_undo_time") - struct.highlight_show_source = config.getboolean( - "general", "highlight_show_source" - ) - struct.hist_file = config.get("general", "hist_file") - struct.editor = config.get("general", "editor") - struct.hist_length = config.getint("general", "hist_length") - struct.hist_duplicates = config.getboolean("general", "hist_duplicates") - struct.flush_output = config.getboolean("general", "flush_output") - struct.default_autoreload = config.getboolean( - "general", "default_autoreload" - ) - struct.import_completion_skiplist = config.get( - "general", "import_completion_skiplist" - ).split(":") - - struct.pastebin_key = get_key_no_doublebind("pastebin") - struct.copy_clipboard_key = get_key_no_doublebind("copy_clipboard") - struct.save_key = get_key_no_doublebind("save") - struct.search_key = get_key_no_doublebind("search") - struct.show_source_key = get_key_no_doublebind("show_source") - struct.suspend_key = get_key_no_doublebind("suspend") - struct.toggle_file_watch_key = get_key_no_doublebind("toggle_file_watch") - struct.undo_key = get_key_no_doublebind("undo") - struct.redo_key = get_key_no_doublebind("redo") - struct.reimport_key = get_key_no_doublebind("reimport") - struct.reverse_incremental_search_key = get_key_no_doublebind( - "reverse_incremental_search" - ) - struct.incremental_search_key = get_key_no_doublebind("incremental_search") - struct.up_one_line_key = get_key_no_doublebind("up_one_line") - struct.down_one_line_key = get_key_no_doublebind("down_one_line") - struct.cut_to_buffer_key = get_key_no_doublebind("cut_to_buffer") - struct.yank_from_buffer_key = get_key_no_doublebind("yank_from_buffer") - struct.clear_word_key = get_key_no_doublebind("clear_word") - struct.backspace_key = get_key_no_doublebind("backspace") - struct.clear_line_key = get_key_no_doublebind("clear_line") - struct.clear_screen_key = get_key_no_doublebind("clear_screen") - struct.delete_key = get_key_no_doublebind("delete") - - struct.left_key = get_key_no_doublebind("left") - struct.right_key = get_key_no_doublebind("right") - struct.end_of_line_key = get_key_no_doublebind("end_of_line") - struct.beginning_of_line_key = get_key_no_doublebind("beginning_of_line") - struct.transpose_chars_key = get_key_no_doublebind("transpose_chars") - struct.exit_key = get_key_no_doublebind("exit") - struct.last_output_key = get_key_no_doublebind("last_output") - struct.edit_config_key = get_key_no_doublebind("edit_config") - struct.edit_current_block_key = get_key_no_doublebind("edit_current_block") - struct.external_editor_key = get_key_no_doublebind("external_editor") - struct.help_key = get_key_no_doublebind("help") - - struct.pastebin_confirm = config.getboolean("general", "pastebin_confirm") - struct.pastebin_url = config.get("general", "pastebin_url") - struct.pastebin_expiry = config.get("general", "pastebin_expiry") - struct.pastebin_helper = config.get("general", "pastebin_helper") - - struct.cli_suggestion_width = config.getfloat("cli", "suggestion_width") - struct.cli_trim_prompts = config.getboolean("cli", "trim_prompts") - - struct.complete_magic_methods = config.getboolean( - "general", "complete_magic_methods" - ) - struct.autocomplete_mode = AutocompleteModes.from_string( - config.get("general", "autocomplete_mode") - ) - struct.save_append_py = config.getboolean("general", "save_append_py") - - struct.curtsies_list_above = config.getboolean("curtsies", "list_above") - struct.curtsies_right_arrow_completion = config.getboolean( - "curtsies", "right_arrow_completion" - ) - struct.unicode_box = config.getboolean("general", "unicode_box") - - color_scheme_name = config.get("general", "color_scheme") - - default_colors = { - "keyword": "y", - "name": "c", - "comment": "b", - "string": "m", - "error": "r", - "number": "G", - "operator": "Y", - "punctuation": "y", - "token": "C", - "background": "d", - "output": "w", - "main": "c", - "paren": "R", - "prompt": "c", - "prompt_more": "g", - "right_arrow_suggestion": "K", - } - - if color_scheme_name == "default": - struct.color_scheme = default_colors - else: - struct.color_scheme = dict() - - path = get_config_home() / f"{color_scheme_name}.theme" - try: - load_theme(struct, path, struct.color_scheme, default_colors) - except OSError: - sys.stderr.write( - f"Could not load theme '{color_scheme_name}' from {path}.\n" - ) - sys.exit(1) - except UnknownColorCode as ucc: - sys.stderr.write( - f"Theme '{color_scheme_name}' contains invalid color: {ucc.key} = {ucc.color}.\n" + self.cli_suggestion_width = config.getfloat("cli", "suggestion_width") + self.cli_trim_prompts = config.getboolean("cli", "trim_prompts") + + self.complete_magic_methods = config.getboolean( + "general", "complete_magic_methods" + ) + self.autocomplete_mode = ( + AutocompleteModes.from_string( + config.get("general", "autocomplete_mode") ) - sys.exit(1) + or default_completion + ) + self.save_append_py = config.getboolean("general", "save_append_py") - # expand path of history file - struct.hist_file = Path(struct.hist_file).expanduser() - - # verify completion mode - if struct.autocomplete_mode is None: - struct.autocomplete_mode = default_completion - - # set box drawing characters - if struct.unicode_box and supports_box_chars(): - struct.left_border = "│" - struct.right_border = "│" - struct.top_border = "─" - struct.bottom_border = "─" - struct.left_bottom_corner = "└" - struct.right_bottom_corner = "┘" - struct.left_top_corner = "┌" - struct.right_top_corner = "┐" - else: - struct.left_border = "|" - struct.right_border = "|" - struct.top_border = "-" - struct.bottom_border = "-" - struct.left_bottom_corner = "+" - struct.right_bottom_corner = "+" - struct.left_top_corner = "+" - struct.right_top_corner = "+" - - -def load_theme(struct, path, colors, default_colors): + self.curtsies_list_above = config.getboolean("curtsies", "list_above") + self.curtsies_right_arrow_completion = config.getboolean( + "curtsies", "right_arrow_completion" + ) + self.unicode_box = config.getboolean("general", "unicode_box") + + color_scheme_name = config.get("general", "color_scheme") + + default_colors = { + "keyword": "y", + "name": "c", + "comment": "b", + "string": "m", + "error": "r", + "number": "G", + "operator": "Y", + "punctuation": "y", + "token": "C", + "background": "d", + "output": "w", + "main": "c", + "paren": "R", + "prompt": "c", + "prompt_more": "g", + "right_arrow_suggestion": "K", + } + + if color_scheme_name == "default": + self.color_scheme = default_colors + else: + self.color_scheme = dict() + + path = get_config_home() / f"{color_scheme_name}.theme" + try: + load_theme(path, self.color_scheme, default_colors) + except OSError: + sys.stderr.write( + f"Could not load theme '{color_scheme_name}' from {path}.\n" + ) + sys.exit(1) + except UnknownColorCode as ucc: + sys.stderr.write( + f"Theme '{color_scheme_name}' contains invalid color: {ucc.key} = {ucc.color}.\n" + ) + sys.exit(1) + + # set box drawing characters + if self.unicode_box and supports_box_chars(): + self.left_border = "│" + self.right_border = "│" + self.top_border = "─" + self.bottom_border = "─" + self.left_bottom_corner = "└" + self.right_bottom_corner = "┘" + self.left_top_corner = "┌" + self.right_top_corner = "┐" + else: + self.left_border = "|" + self.right_border = "|" + self.top_border = "-" + self.bottom_border = "-" + self.left_bottom_corner = "+" + self.right_bottom_corner = "+" + self.left_top_corner = "+" + self.right_top_corner = "+" + + +def load_theme( + path: Path, + colors: MutableMapping[str, str], + default_colors: Mapping[str, str], +) -> None: theme = ConfigParser() with open(path) as f: theme.read_file(f) diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index 76db5d293..2d2e5e820 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -9,33 +9,25 @@ class TestConfig(unittest.TestCase): - def load_temp_config(self, content, struct=None): + def load_temp_config(self, content): """Write config to a temporary file and load it.""" - if struct is None: - struct = config.Struct() - with tempfile.NamedTemporaryFile() as f: f.write(content.encode("utf8")) f.flush() - config.loadini(struct, f.name) - - return struct + return config.Config(f.name) def test_load_theme(self): - struct = config.Struct() - struct.color_scheme = dict() - config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, dict()) + color_scheme = dict() + config.load_theme(TEST_THEME_PATH, color_scheme, dict()) expected = {"keyword": "y"} - self.assertEqual(struct.color_scheme, expected) + self.assertEqual(color_scheme, expected) defaults = {"name": "c"} expected.update(defaults) - config.load_theme( - struct, TEST_THEME_PATH, struct.color_scheme, defaults - ) - self.assertEqual(struct.color_scheme, expected) + config.load_theme(TEST_THEME_PATH, color_scheme, defaults) + self.assertEqual(color_scheme, expected) def test_keybindings_default_contains_no_duplicates(self): struct = self.load_temp_config("") diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 7ce91d67a..adc63f155 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -25,8 +25,7 @@ def setup_config(): - config_struct = config.Struct() - config.loadini(config_struct, TEST_CONFIG) + config_struct = config.Config(TEST_CONFIG) config_struct.cli_suggestion_width = 1 return config_struct diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index f7798ae6f..2ed33097b 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -27,8 +27,7 @@ def setup_config(conf): - config_struct = config.Struct() - config.loadini(config_struct, TEST_CONFIG) + config_struct = config.Config(TEST_CONFIG) for key, value in conf.items(): if not hasattr(config_struct, key): raise ValueError(f"{key!r} is not a valid config attribute") diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 2d33430fe..12275e227 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -18,8 +18,7 @@ def setup_config(conf): - config_struct = config.Struct() - config.loadini(config_struct, TEST_CONFIG) + config_struct = config.Config(TEST_CONFIG) if conf is not None and "autocomplete_mode" in conf: config_struct.autocomplete_mode = conf["autocomplete_mode"] return config_struct diff --git a/doc/sphinx/source/simplerepl.py b/doc/sphinx/source/simplerepl.py index e0bddb480..8a8dda74e 100644 --- a/doc/sphinx/source/simplerepl.py +++ b/doc/sphinx/source/simplerepl.py @@ -28,7 +28,7 @@ import logging from bpython import translations -from bpython.config import Struct, loadini, default_config_path +from bpython.config import Config, default_config_path from bpython.curtsiesfrontend import events as bpythonevents from bpython.curtsiesfrontend.repl import BaseRepl from bpython.importcompletion import ModuleGatherer @@ -117,8 +117,7 @@ def get_input(self): def main(args=None, locals_=None, banner=None): translations.init() - config = Struct() - loadini(config, default_config_path()) + config = Config(default_config_path()) module_gatherer = ModuleGatherer() while module_gatherer.find_coroutine(): pass From dec96ef8ba0b49eb59af57ed78376fa300b3b874 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 18 May 2021 00:08:02 +0200 Subject: [PATCH 1277/1650] Replace Struct with a dataclass --- bpython/cli.py | 31 +++++++++++++++++-------------- bpython/config.py | 5 ----- requirements.txt | 1 + setup.cfg | 1 + 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 6d570c613..8add9f743 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -40,23 +40,23 @@ # - View source doesn't work on windows unless you install the less program (From GnuUtils or Cygwin) -import platform -import os -import sys import curses +import errno +import functools import math +import os +import platform import re -import time -import functools - import struct +import sys +import time +import unicodedata +from dataclasses import dataclass if platform.system() != "Windows": import signal # Windows does not have job control import termios # Windows uses curses import fcntl # Windows uses curses -import unicodedata -import errno # These are used for syntax highlighting @@ -67,7 +67,7 @@ from .formatter import BPythonFormatter # This for config -from .config import Struct, getpreferredencoding +from .config import getpreferredencoding # This for keys from .keys import cli_key_dispatch as key_dispatch @@ -90,6 +90,13 @@ # --- +@dataclass +class ShowListState: + cols: int = 0 + rows: int = 0 + wl: int = 0 + + def calculate_screen_lines(tokens, width, cursor=0): """Given a stream of tokens and a screen width plus an optional initial cursor position, return the amount of needed lines on the @@ -1254,11 +1261,7 @@ def write(self, s): def show_list( self, items, arg_pos, topline=None, formatter=None, current_item=None ): - - shared = Struct() - shared.cols = 0 - shared.rows = 0 - shared.wl = 0 + shared = ShowListState() y, x = self.scr.getyx() h, w = self.scr.getmaxyx() down = y < h // 2 diff --git a/bpython/config.py b/bpython/config.py index 399782de2..3e0aac997 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -42,11 +42,6 @@ def __init__(self, key: str, color: str) -> None: self.color = color -class Struct: - """Simple class for instantiating objects we can add arbitrary attributes - to and use for various arbitrary things.""" - - def getpreferredencoding() -> str: """Get the user's preferred encoding.""" return locale.getpreferredencoding() or sys.getdefaultencoding() diff --git a/requirements.txt b/requirements.txt index 7f56dc0fd..a989bdf40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ Pygments backports.cached-property; python_version < "3.8" curtsies >=0.3.5 cwcwidth +dataclasses; python_version < "3.7" greenlet pyxdg requests diff --git a/setup.cfg b/setup.cfg index 7c41b4788..89b6cde61 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,7 @@ packages = install_requires = backports.cached-property; python_version < "3.8" curtsies >=0.3.5 + dataclasses; python_version < "3.7" cwcwidth greenlet pygments From 1916d28761e863479c5ec168dde932324bebe18e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 18 May 2021 00:08:35 +0200 Subject: [PATCH 1278/1650] Add type annotations --- bpython/config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/config.py b/bpython/config.py index 3e0aac997..b9e739957 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -75,7 +75,9 @@ def default_editor() -> str: return os.environ.get("VISUAL", os.environ.get("EDITOR", "vi")) -def fill_config_with_default_values(config, default_values): +def fill_config_with_default_values( + config: ConfigParser, default_values: Mapping[str, Mapping[str, Any]] +) -> None: for section in default_values.keys(): if not config.has_section(section): config.add_section(section) From 41561936d54e1b5ab2a879141dd61a09802f42f9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 18 May 2021 11:01:39 +0200 Subject: [PATCH 1279/1650] Make defaults a class variable --- bpython/config.py | 241 +++++++++++++++++++++++----------------------- 1 file changed, 120 insertions(+), 121 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index b9e739957..0fc60f562 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -88,105 +88,120 @@ def fill_config_with_default_values( class Config: - def __init__(self, config_path: Path): + default_colors = { + "keyword": "y", + "name": "c", + "comment": "b", + "string": "m", + "error": "r", + "number": "G", + "operator": "Y", + "punctuation": "y", + "token": "C", + "background": "d", + "output": "w", + "main": "c", + "paren": "R", + "prompt": "c", + "prompt_more": "g", + "right_arrow_suggestion": "K", + } + + defaults: Dict[str, Dict[str, Any]] = { + "general": { + "arg_spec": True, + "auto_display_list": True, + "autocomplete_mode": default_completion, + "color_scheme": "default", + "complete_magic_methods": True, + "dedent_after": 1, + "default_autoreload": False, + "editor": default_editor(), + "flush_output": True, + "import_completion_skiplist": ":".join( + ( + # version tracking + ".git", + ".svn", + ".hg" + # XDG + ".config", + ".local", + ".share", + # nodejs + "node_modules", + # PlayOnLinux + "PlayOnLinux's virtual drives", + # wine + "dosdevices", + # Python byte code cache + "__pycache__", + ) + ), + "highlight_show_source": True, + "hist_duplicates": True, + "hist_file": "~/.pythonhist", + "hist_length": 1000, + "paste_time": 0.02, + "pastebin_confirm": True, + "pastebin_expiry": "1week", + "pastebin_helper": "", + "pastebin_url": "https://bpaste.net", + "save_append_py": False, + "single_undo_time": 1.0, + "syntax": True, + "tab_length": 4, + "unicode_box": True, + }, + "keyboard": { + "backspace": "C-h", + "beginning_of_line": "C-a", + "clear_line": "C-u", + "clear_screen": "C-l", + "clear_word": "C-w", + "copy_clipboard": "F10", + "cut_to_buffer": "C-k", + "delete": "C-d", + "down_one_line": "C-n", + "edit_config": "F3", + "edit_current_block": "C-x", + "end_of_line": "C-e", + "exit": "", + "external_editor": "F7", + "help": "F1", + "incremental_search": "M-s", + "last_output": "F9", + "left": "C-b", + "pastebin": "F8", + "redo": "C-g", + "reimport": "F6", + "reverse_incremental_search": "M-r", + "right": "C-f", + "save": "C-s", + "search": "C-o", + "show_source": "F2", + "suspend": "C-z", + "toggle_file_watch": "F5", + "transpose_chars": "C-t", + "undo": "C-r", + "up_one_line": "C-p", + "yank_from_buffer": "C-y", + }, + "cli": { + "suggestion_width": 0.8, + "trim_prompts": False, + }, + "curtsies": { + "list_above": False, + "right_arrow_completion": True, + }, + } + + def __init__(self, config_path: Path) -> None: """Loads .ini configuration file and stores its values.""" config = ConfigParser() - defaults: Dict[str, Dict[str, Any]] = { - "general": { - "arg_spec": True, - "auto_display_list": True, - "autocomplete_mode": default_completion, - "color_scheme": "default", - "complete_magic_methods": True, - "dedent_after": 1, - "default_autoreload": False, - "editor": default_editor(), - "flush_output": True, - "import_completion_skiplist": ":".join( - ( - # version tracking - ".git", - ".svn", - ".hg" - # XDG - ".config", - ".local", - ".share", - # nodejs - "node_modules", - # PlayOnLinux - "PlayOnLinux's virtual drives", - # wine - "dosdevices", - # Python byte code cache - "__pycache__", - ) - ), - "highlight_show_source": True, - "hist_duplicates": True, - "hist_file": "~/.pythonhist", - "hist_length": 1000, - "paste_time": 0.02, - "pastebin_confirm": True, - "pastebin_expiry": "1week", - "pastebin_helper": "", - "pastebin_url": "https://bpaste.net", - "save_append_py": False, - "single_undo_time": 1.0, - "syntax": True, - "tab_length": 4, - "unicode_box": True, - }, - "keyboard": { - "backspace": "C-h", - "beginning_of_line": "C-a", - "clear_line": "C-u", - "clear_screen": "C-l", - "clear_word": "C-w", - "copy_clipboard": "F10", - "cut_to_buffer": "C-k", - "delete": "C-d", - "down_one_line": "C-n", - "edit_config": "F3", - "edit_current_block": "C-x", - "end_of_line": "C-e", - "exit": "", - "external_editor": "F7", - "help": "F1", - "incremental_search": "M-s", - "last_output": "F9", - "left": "C-b", - "pastebin": "F8", - "redo": "C-g", - "reimport": "F6", - "reverse_incremental_search": "M-r", - "right": "C-f", - "save": "C-s", - "search": "C-o", - "show_source": "F2", - "suspend": "C-z", - "toggle_file_watch": "F5", - "transpose_chars": "C-t", - "undo": "C-r", - "up_one_line": "C-p", - "yank_from_buffer": "C-y", - }, - "cli": { - "suggestion_width": 0.8, - "trim_prompts": False, - }, - "curtsies": { - "list_above": False, - "right_arrow_completion": True, - }, - } - - default_keys_to_commands = { - value: key for (key, value) in defaults["keyboard"].items() - } - - fill_config_with_default_values(config, defaults) + fill_config_with_default_values(config, self.defaults) try: config.read(config_path) except UnicodeDecodeError as e: @@ -199,8 +214,12 @@ def __init__(self, config_path: Path): ) sys.exit(1) - def get_key_no_doublebind(command): - default_commands_to_keys = defaults["keyboard"] + default_keys_to_commands = { + value: key for (key, value) in self.defaults["keyboard"].items() + } + + def get_key_no_doublebind(command: str) -> str: + default_commands_to_keys = self.defaults["keyboard"] requested_key = config.get("keyboard", command) try: @@ -209,7 +228,7 @@ def get_key_no_doublebind(command): if default_commands_to_keys[default_command] == config.get( "keyboard", default_command ): - setattr(self, "%s_key" % default_command, "") + setattr(self, f"{default_command}_key", "") except KeyError: pass @@ -307,34 +326,14 @@ def get_key_no_doublebind(command): self.unicode_box = config.getboolean("general", "unicode_box") color_scheme_name = config.get("general", "color_scheme") - - default_colors = { - "keyword": "y", - "name": "c", - "comment": "b", - "string": "m", - "error": "r", - "number": "G", - "operator": "Y", - "punctuation": "y", - "token": "C", - "background": "d", - "output": "w", - "main": "c", - "paren": "R", - "prompt": "c", - "prompt_more": "g", - "right_arrow_suggestion": "K", - } - if color_scheme_name == "default": - self.color_scheme = default_colors + self.color_scheme = self.default_colors else: self.color_scheme = dict() path = get_config_home() / f"{color_scheme_name}.theme" try: - load_theme(path, self.color_scheme, default_colors) + load_theme(path, self.color_scheme, self.default_colors) except OSError: sys.stderr.write( f"Could not load theme '{color_scheme_name}' from {path}.\n" From 2dfe4ff9dc72474317792f8efb110e755a268818 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 18 May 2021 11:02:30 +0200 Subject: [PATCH 1280/1650] Reduce verbosity --- bpython/config.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 0fc60f562..2aee538d9 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -346,24 +346,20 @@ def get_key_no_doublebind(command: str) -> str: sys.exit(1) # set box drawing characters - if self.unicode_box and supports_box_chars(): - self.left_border = "│" - self.right_border = "│" - self.top_border = "─" - self.bottom_border = "─" - self.left_bottom_corner = "└" - self.right_bottom_corner = "┘" - self.left_top_corner = "┌" - self.right_top_corner = "┐" - else: - self.left_border = "|" - self.right_border = "|" - self.top_border = "-" - self.bottom_border = "-" - self.left_bottom_corner = "+" - self.right_bottom_corner = "+" - self.left_top_corner = "+" - self.right_top_corner = "+" + ( + self.left_border, + self.right_border, + self.top_border, + self.bottom_border, + self.left_bottom_corner, + self.right_bottom_corner, + self.left_top_corner, + self.right_top_corner, + ) = ( + ("│", "│", "─", "─", "└", "┘", "┌", "┐") + if self.unicode_box and supports_box_chars() + else ("|", "|", "-", "-", "+", "+", "+", "+") + ) def load_theme( From a7609fd0ebc527491273b9d7a4dca3eba15bfab4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 21 May 2021 09:20:35 +0200 Subject: [PATCH 1281/1650] Clean up --- bpython/config.py | 1 - bpython/repl.py | 12 ++++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 2aee538d9..f4de762d6 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -224,7 +224,6 @@ def get_key_no_doublebind(command: str) -> str: try: default_command = default_keys_to_commands[requested_key] - if default_commands_to_keys[default_command] == config.get( "keyboard", default_command ): diff --git a/bpython/repl.py b/bpython/repl.py index c1ba03617..ba9acf4d8 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -859,8 +859,8 @@ def pastebin(self, s=None): _("Pastebin buffer? (y/N) ") ): self.interact.notify(_("Pastebin aborted.")) - return - return self.do_pastebin(s) + else: + return self.do_pastebin(s) def do_pastebin(self, s): """Actually perform the upload.""" @@ -1119,10 +1119,7 @@ def open_in_external_editor(self, filename): def edit_config(self): if not self.config.config_path.is_file(): if self.interact.confirm( - _( - "Config file does not exist - create " - "new from default? (y/N)" - ) + _("Config file does not exist - create new from default? (y/N)") ): try: default_config = pkgutil.get_data( @@ -1148,8 +1145,7 @@ def edit_config(self): if self.open_in_external_editor(self.config.config_path): self.interact.notify( _( - "bpython config file edited. Restart " - "bpython for changes to take effect." + "bpython config file edited. Restart bpython for changes to take effect." ) ) except OSError as e: From feae0f15a3165cc5c02821fafe8c5f574c16f78b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 21 May 2021 09:22:12 +0200 Subject: [PATCH 1282/1650] Evaluate tuple() and list() --- bpython/simpleeval.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 801da318a..ef6dd53b9 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -63,6 +63,7 @@ def safe_eval(expr, namespace): # * new docstring describing different functionality # * looks up names from namespace # * indexing syntax is allowed +# * evaluates tuple() and list() def simple_eval(node_or_string, namespace=None): """ Safely evaluate an expression node or a string containing a Python @@ -111,6 +112,22 @@ def _convert(node): ): return set() + # this is a deviation from literal_eval: we evaluate tuple() and list() + elif ( + isinstance(node, ast.Call) + and isinstance(node.func, ast.Name) + and node.func.id == "tuple" + and node.args == node.keywords == [] + ): + return tuple() + elif ( + isinstance(node, ast.Call) + and isinstance(node.func, ast.Name) + and node.func.id == "list" + and node.args == node.keywords == [] + ): + return list() + # this is a deviation from literal_eval: we allow non-literals elif isinstance(node, _name_type_nodes): try: From bc7ea75f21f11c14c0615008494eab4b7403af38 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 21 May 2021 21:13:04 +0200 Subject: [PATCH 1283/1650] Replace ps1/ps2 with the default if values are not usable (fixes #896) This is a workaround and should be replaced with a better solution in the future. --- bpython/curtsiesfrontend/repl.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d35317303..faacc9c4f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -13,6 +13,7 @@ from enum import Enum import blessings +import cwcwidth import greenlet from curtsies import ( FSArray, @@ -275,6 +276,14 @@ def _find_module(self, fullname, path=None): return ImportLoader(self.watcher, loader) +def _process_ps(ps, default_ps: str): + """Replace ps1/ps2 with the default if the user specified value contains control characters.""" + if not isinstance(ps, str): + return ps + + return ps if cwcwidth.wcswidth(ps) >= 0 else default_ps + + class BaseRepl(Repl): """Python Repl @@ -2051,6 +2060,14 @@ def process(): return "\n".join(process()) + @property + def ps1(self): + return _process_ps(super().ps1, ">>> ") + + @property + def ps2(self): + return _process_ps(super().ps2, "... ") + def is_nop(char): return unicodedata.category(str(char)) == "Cc" From 7b6ab950402495454ef7ebf5877589a4c26f897e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 21 May 2021 21:17:09 +0200 Subject: [PATCH 1284/1650] Use f-string --- bpython/curtsiesfrontend/repl.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index faacc9c4f..ba205ad27 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1729,13 +1729,11 @@ def in_paste_mode(self): self.update_completion() def __repr__(self): - s = "" - s += "<" + repr(type(self)) + "\n" - s += " cursor_offset:" + repr(self.cursor_offset) + "\n" - s += " num display lines:" + repr(len(self.display_lines)) + "\n" - s += " lines scrolled down:" + repr(self.scroll_offset) + "\n" - s += ">" - return s + return f"""<{type(self)} + cursor_offset: {self.cursor_offset} + num display lines: {len(self.display_lines)} + lines scrolled down: {self.scroll_offset} +>""" def _get_current_line(self): return self._current_line From fc3e65f0a6f8ae21f41038abfe651905c9e79193 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 21 May 2021 21:27:55 +0200 Subject: [PATCH 1285/1650] Use property decorator --- bpython/curtsiesfrontend/repl.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index ba205ad27..1c09a196c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1735,9 +1735,15 @@ def __repr__(self): lines scrolled down: {self.scroll_offset} >""" - def _get_current_line(self): + @property + def current_line(self): + """The current line""" return self._current_line + @current_line.setter + def current_line(self, value): + self._set_current_line(value) + def _set_current_line( self, line, @@ -1758,13 +1764,15 @@ def _set_current_line( self.special_mode = None self.unhighlight_paren() - current_line = property( - _get_current_line, _set_current_line, None, "The current line" - ) - - def _get_cursor_offset(self): + @property + def cursor_offset(self): + """The current cursor offset from the front of the "line".""" return self._cursor_offset + @cursor_offset.setter + def cursor_offset(self, value): + self._set_cursor_offset(value) + def _set_cursor_offset( self, offset, @@ -1787,13 +1795,6 @@ def _set_cursor_offset( self.update_completion() self.unhighlight_paren() - cursor_offset = property( - _get_cursor_offset, - _set_cursor_offset, - None, - "The current cursor offset from the front of the " "line", - ) - def echo(self, msg, redraw=True): """ Notification that redrawing the current line is necessary (we don't From 62833ff51e4c7167d0c84e8605ef584294959c41 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 21 May 2021 21:36:31 +0200 Subject: [PATCH 1286/1650] Assign color_scheme only once --- bpython/config.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index f4de762d6..48686610e 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -324,12 +324,11 @@ def get_key_no_doublebind(command: str) -> str: ) self.unicode_box = config.getboolean("general", "unicode_box") + self.color_scheme = dict() color_scheme_name = config.get("general", "color_scheme") if color_scheme_name == "default": - self.color_scheme = self.default_colors + self.color_scheme.update(self.default_colors) else: - self.color_scheme = dict() - path = get_config_home() / f"{color_scheme_name}.theme" try: load_theme(path, self.color_scheme, self.default_colors) From 3e8711c1290ad619e413ac4dc74d27928e3a5acb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 21 May 2021 21:42:44 +0200 Subject: [PATCH 1287/1650] Simplify comparisons --- bpython/line.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 14a2cf8d7..7ad42d63e 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -22,7 +22,7 @@ def current_word(cursor_offset: int, line: str) -> Optional[LinePart]: end = pos word = None for m in current_word_re.finditer(line): - if m.start(1) < pos and m.end(1) >= pos: + if m.start(1) < pos <= m.end(1): start = m.start(1) end = m.end(1) word = m.group(1) @@ -37,7 +37,7 @@ def current_word(cursor_offset: int, line: str) -> Optional[LinePart]: def current_dict_key(cursor_offset: int, line: str) -> Optional[LinePart]: """If in dictionary completion, return the current key""" for m in current_dict_key_re.finditer(line): - if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: + if m.start(1) <= cursor_offset <= m.end(1): return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -48,7 +48,7 @@ def current_dict_key(cursor_offset: int, line: str) -> Optional[LinePart]: def current_dict(cursor_offset: int, line: str) -> Optional[LinePart]: """If in dictionary completion, return the dict that should be used""" for m in current_dict_re.finditer(line): - if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset: + if m.start(2) <= cursor_offset <= m.end(2): return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -67,7 +67,7 @@ def current_string(cursor_offset: int, line: str) -> Optional[LinePart]: string is a string based on previous lines in the buffer.""" for m in current_string_re.finditer(line): i = 3 if m.group(3) else 4 - if m.start(i) <= cursor_offset and m.end(i) >= cursor_offset: + if m.start(i) <= cursor_offset <= m.end(i): return LinePart(m.start(i), m.end(i), m.group(i)) return None @@ -108,10 +108,7 @@ def current_object_attribute( matches = current_object_attribute_re.finditer(word) next(matches) for m in matches: - if ( - m.start(1) + start <= cursor_offset - and m.end(1) + start >= cursor_offset - ): + if m.start(1) + start <= cursor_offset <= m.end(1) + start: return LinePart(m.start(1) + start, m.end(1) + start, m.group(1)) return None @@ -131,8 +128,8 @@ def current_from_import_from( """ # TODO allow for as's for m in current_from_import_from_re.finditer(line): - if (m.start(1) < cursor_offset and m.end(1) >= cursor_offset) or ( - m.start(2) < cursor_offset and m.end(2) >= cursor_offset + if (m.start(1) < cursor_offset <= m.end(1)) or ( + m.start(2) < cursor_offset <= m.end(2) ): return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -162,7 +159,7 @@ def current_from_import_import( ): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) - if start < cursor_offset and end >= cursor_offset: + if start < cursor_offset <= end: return LinePart(start, end, m.group(1)) return None @@ -185,7 +182,7 @@ def current_import(cursor_offset: int, line: str) -> Optional[LinePart]: ): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) - if start < cursor_offset and end >= cursor_offset: + if start < cursor_offset <= end: return LinePart(start, end, m.group(1)) return None @@ -198,7 +195,7 @@ def current_method_definition_name( ) -> Optional[LinePart]: """The name of a method being defined""" for m in current_method_definition_name_re.finditer(line): - if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: + if m.start(1) <= cursor_offset <= m.end(1): return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -209,7 +206,7 @@ def current_method_definition_name( def current_single_word(cursor_offset: int, line: str) -> Optional[LinePart]: """the un-dotted word just before or under the cursor""" for m in current_single_word_re.finditer(line): - if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: + if m.start(1) <= cursor_offset <= m.end(1): return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -238,6 +235,6 @@ def current_expression_attribute( """If after a dot, the attribute being completed""" # TODO replace with more general current_expression_attribute for m in current_expression_attribute_re.finditer(line): - if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset: + if m.start(1) <= cursor_offset <= m.end(1): return LinePart(m.start(1), m.end(1), m.group(1)) return None From 9d33cf7136a26b89f51c2050a094d12bf6365dcd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 21 May 2021 21:53:23 +0200 Subject: [PATCH 1288/1650] Call super's __init__ --- bpython/curtsiesfrontend/filewatch.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 6657fac17..314767a53 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -26,6 +26,8 @@ def __init__(self, paths, on_change): for path in paths: self._add_module(path) + super().__init__() + def reset(self): self.dirs = defaultdict(set) del self.modules_to_add_later[:] From f7a7181c820b8e8d0061f8c37bf19d577fdddebd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 21 May 2021 21:53:34 +0200 Subject: [PATCH 1289/1650] Split only once --- bpython/curtsiesfrontend/repl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 1c09a196c..f38304e9a 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -2077,7 +2077,8 @@ def tabs_to_spaces(line): def _last_word(line): - return line.split().pop() if line.split() else "" + split_line = line.split() + return split_line.pop() if split_line else "" def compress_paste_event(paste_event): From 639c6494bef44a2546784eb657dae63579e61688 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 21 May 2021 21:58:45 +0200 Subject: [PATCH 1290/1650] Re-organize imports --- bpython/curtsies.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 69d3f175f..b33691a58 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -3,19 +3,18 @@ import sys import curtsies -import curtsies.window -import curtsies.input import curtsies.events +import curtsies.input +import curtsies.window -from .curtsiesfrontend.repl import BaseRepl +from . import args as bpargs, translations, inspection +from .config import Config +from .curtsiesfrontend import events from .curtsiesfrontend.coderunner import SystemExitFromCodeRunner from .curtsiesfrontend.interpreter import Interp -from . import args as bpargs -from . import translations -from .translations import _ -from .curtsiesfrontend import events as bpythonevents -from . import inspection +from .curtsiesfrontend.repl import BaseRepl from .repl import extract_exit_value +from .translations import _ logger = logging.getLogger(__name__) @@ -34,20 +33,18 @@ def __init__(self, config, locals_, banner, interp=None): ) self._request_refresh = self.input_generator.event_trigger( - bpythonevents.RefreshRequestEvent + events.RefreshRequestEvent ) self._schedule_refresh = self.input_generator.scheduled_event_trigger( - bpythonevents.ScheduledRefreshRequestEvent + events.ScheduledRefreshRequestEvent ) self._request_reload = self.input_generator.threadsafe_event_trigger( - bpythonevents.ReloadEvent + events.ReloadEvent ) self.interrupting_refresh = ( self.input_generator.threadsafe_event_trigger(lambda: None) ) - self.request_undo = self.input_generator.event_trigger( - bpythonevents.UndoEvent - ) + self.request_undo = self.input_generator.event_trigger(events.UndoEvent) with self.input_generator: pass # temp hack to get .original_stty @@ -103,7 +100,7 @@ def mainloop(self, interactive=True, paste=None): self.initialize_interp() # run startup file - self.process_event(bpythonevents.RunStartupFileEvent()) + self.process_event(events.RunStartupFileEvent()) # handle paste if paste: From 08d32834e31fe40624c191ba28d6117902af661a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 24 May 2021 12:10:39 +0200 Subject: [PATCH 1291/1650] Remove argument which is the same as the default --- bpython/importcompletion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 039335351..02255a6b3 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -101,7 +101,7 @@ def attr_matches( def module_attr_matches(self, name: str) -> Set[str]: """Only attributes which are modules to replace name with""" - return self.attr_matches(name, prefix="", only_modules=True) + return self.attr_matches(name, only_modules=True) def complete(self, cursor_offset: int, line: str) -> Optional[Set[str]]: """Construct a full list of possibly completions for imports.""" From 213000739176348a992f00467a469c2da656a1b0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 24 May 2021 12:11:25 +0200 Subject: [PATCH 1292/1650] Move str->Path converstion to __init__ --- bpython/importcompletion.py | 42 +++++++++++++++++---------- bpython/test/test_importcompletion.py | 6 ++-- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 02255a6b3..8cb333e89 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -26,7 +26,7 @@ import sys import warnings from pathlib import Path -from typing import Optional, Set, Generator, Tuple, List +from typing import Optional, Set, Generator, Tuple, Sequence, Iterable, Union from .line import ( current_word, @@ -49,15 +49,31 @@ class ModuleGatherer: - def __init__(self, path: Optional[Path] = None, skiplist=None) -> None: - # The cached list of all known modules + def __init__( + self, + paths: Optional[Iterable[Union[str, Path]]] = None, + skiplist: Optional[Sequence[str]] = None, + ) -> None: + """Initialize module gatherer with all modules in `paths`, which should be a list of + directory names. If `paths` is not given, `sys.path` will be used.""" + + # Cached list of all known modules self.modules: Set[str] = set() - # List of (st_dev, st_ino) to compare against so that paths are not repeated + # Set of (st_dev, st_ino) to compare against so that paths are not repeated self.paths: Set[Tuple[int, int]] = set() # Patterns to skip - self.skiplist = skiplist if skiplist is not None else tuple() + self.skiplist: Sequence[str] = ( + skiplist if skiplist is not None else tuple() + ) self.fully_loaded = False - self.find_iterator = self.find_all_modules(path) + + if paths is None: + self.modules.update(sys.builtin_module_names) + paths = sys.path + + self.find_iterator = self.find_all_modules( + (Path(p).resolve() if p else Path.cwd() for p in sys.path) + ) def module_matches(self, cw: str, prefix: str = "") -> Set[str]: """Modules names to replace cw with""" @@ -191,8 +207,7 @@ def find_modules(self, path: Path) -> Generator[str, None, None]: except (ImportError, OSError, SyntaxError): continue except UnicodeEncodeError: - # Happens with Python 3 when there is a filename in some - # invalid encoding + # Happens with Python 3 when there is a filename in some invalid encoding continue else: if is_package: @@ -205,16 +220,13 @@ def find_modules(self, path: Path) -> Generator[str, None, None]: yield f"{name}.{subname}" yield name - def find_all_modules(self, path=None): + def find_all_modules( + self, paths: Iterable[Path] + ) -> Generator[None, None, None]: """Return a list with all modules in `path`, which should be a list of directory names. If path is not given, sys.path will be used.""" - if path is None: - self.modules.update(sys.builtin_module_names) - path = sys.path - - for p in path: - p = Path(p).resolve() if p else Path.cwd() + for p in paths: for module in self.find_modules(p): self.modules.add(module) yield diff --git a/bpython/test/test_importcompletion.py b/bpython/test/test_importcompletion.py index 3016daf2c..814d3c312 100644 --- a/bpython/test/test_importcompletion.py +++ b/bpython/test/test_importcompletion.py @@ -162,9 +162,7 @@ def setUp(self): base_path / "Right", target_is_directory=True ) - self.module_gatherer = ModuleGatherer( - [os.path.abspath(import_test_folder)] - ) + self.module_gatherer = ModuleGatherer((base_path.absolute(),)) while self.module_gatherer.find_coroutine(): pass @@ -211,7 +209,7 @@ def test_issue_847(self): (base_path / "xyzzy" / "plugh" / "bar.py").touch() (base_path / "xyzzy" / "plugh" / "foo.py").touch() - module_gatherer = ModuleGatherer([base_path.absolute()]) + module_gatherer = ModuleGatherer((base_path.absolute(),)) while module_gatherer.find_coroutine(): pass From eb8664d537322701e74dd5fcb68864778e4bc1af Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 24 May 2021 12:11:38 +0200 Subject: [PATCH 1293/1650] Add return type --- bpython/importcompletion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 8cb333e89..eba24ba92 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -231,7 +231,7 @@ def find_all_modules( self.modules.add(module) yield - def find_coroutine(self): + def find_coroutine(self) -> Optional[bool]: if self.fully_loaded: return None From d77dd9866c2968c0f328cccf6c48adc64e090719 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 24 May 2021 12:21:23 +0200 Subject: [PATCH 1294/1650] Fix argument --- bpython/importcompletion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index eba24ba92..14262db4d 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -72,7 +72,7 @@ def __init__( paths = sys.path self.find_iterator = self.find_all_modules( - (Path(p).resolve() if p else Path.cwd() for p in sys.path) + (Path(p).resolve() if p else Path.cwd() for p in paths) ) def module_matches(self, cw: str, prefix: str = "") -> Set[str]: From 6b8c0499e8951205f97f4f45fd560fe351b22deb Mon Sep 17 00:00:00 2001 From: Kadermiyanyedi Date: Sun, 23 May 2021 21:40:59 +0300 Subject: [PATCH 1295/1650] Fedora installation method added --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index eb9112463..8a26442be 100644 --- a/README.rst +++ b/README.rst @@ -86,6 +86,13 @@ Arch linux uses pacman as the default package manager, and you can use it to ins $ pacman -S bpython +Fedora +~~~~~~~~~~ +Fedora users can install bpython directly from the command line using ``dnf``. + +.. code-block:: bash + + $ dnf install bpython Windows ~~~~~~~ From a905b77cf5ba2193d16786cc42cd55cf5d3132aa Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 24 May 2021 21:35:53 +0200 Subject: [PATCH 1296/1650] Fix type name --- bpython/test/test_autocomplete.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 9b0a4e86d..ad991abe4 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -326,7 +326,7 @@ def test_magic_methods_complete_after_double_underscores(self): ) -Comp = namedtuple("Completion", ["name", "complete"]) +Completion = namedtuple("Completion", ["name", "complete"]) @unittest.skipUnless(has_jedi, "jedi required") @@ -363,7 +363,7 @@ def test_completions_starting_with_different_letters(self): " a", "class Foo:\n a", ["adsf"], - [Comp("Abc", "bc"), Comp("Cbc", "bc")], + [Completion("Abc", "bc"), Completion("Cbc", "bc")], ) self.assertEqual(matches, None) @@ -373,7 +373,7 @@ def test_completions_starting_with_different_cases(self): " a", "class Foo:\n a", ["adsf"], - [Comp("Abc", "bc"), Comp("ade", "de")], + [Completion("Abc", "bc"), Completion("ade", "de")], ) self.assertSetEqual(matches, {"ade"}) From 66e999cac35f5e721a60e69400ab7659e02eeeac Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 24 May 2021 21:48:37 +0200 Subject: [PATCH 1297/1650] Properly override methods --- bpython/curtsies.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index b33691a58..2bd348335 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -32,19 +32,23 @@ def __init__(self, config, locals_, banner, interp=None): extra_bytes_callback=self.input_generator.unget_bytes, ) - self._request_refresh = self.input_generator.event_trigger( + self._request_refresh_callback = self.input_generator.event_trigger( events.RefreshRequestEvent ) - self._schedule_refresh = self.input_generator.scheduled_event_trigger( - events.ScheduledRefreshRequestEvent + self._schedule_refresh_callback = ( + self.input_generator.scheduled_event_trigger( + events.ScheduledRefreshRequestEvent + ) ) - self._request_reload = self.input_generator.threadsafe_event_trigger( - events.ReloadEvent + self._request_reload_callback = ( + self.input_generator.threadsafe_event_trigger(events.ReloadEvent) ) - self.interrupting_refresh = ( + self._interrupting_refresh_callback = ( self.input_generator.threadsafe_event_trigger(lambda: None) ) - self.request_undo = self.input_generator.event_trigger(events.UndoEvent) + self._request_undo_callback = self.input_generator.event_trigger( + events.UndoEvent + ) with self.input_generator: pass # temp hack to get .original_stty @@ -57,6 +61,21 @@ def __init__(self, config, locals_, banner, interp=None): orig_tcattrs=self.input_generator.original_stty, ) + def _request_refresh(self): + return self._request_refresh_callback() + + def _schedule_refresh(self, when="now"): + return self._schedule_refresh_callback(when) + + def _request_reload(self, files_modified=("?",)): + return self._request_reload_callback(files_modified) + + def interrupting_refresh(self): + return self._interrupting_refresh_callback() + + def request_undo(self, n=1): + return self._request_undo_callback(n) + def get_term_hw(self): return self.window.get_term_hw() From 22ebf12b5b2072664c95486db7e47727fb7e8853 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 25 May 2021 13:29:16 +0200 Subject: [PATCH 1298/1650] Clean up --- bpython/line.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 7ad42d63e..e103b9d97 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -11,18 +11,16 @@ from .lazyre import LazyReCompile LinePart = namedtuple("LinePart", ["start", "stop", "word"]) - current_word_re = LazyReCompile(r"(? Optional[LinePart]: """the object.attribute.attribute just before or under the cursor""" - pos = cursor_offset - start = pos - end = pos + start = cursor_offset + end = cursor_offset word = None for m in current_word_re.finditer(line): - if m.start(1) < pos <= m.end(1): + if m.start(1) < cursor_offset <= m.end(1): start = m.start(1) end = m.end(1) word = m.group(1) @@ -82,12 +80,11 @@ def current_object(cursor_offset: int, line: str) -> Optional[LinePart]: if match is None: return None start, end, word = match - s = "" - for m in current_object_re.finditer(word): - if m.end(1) + start < cursor_offset: - if s: - s += "." - s += m.group(1) + s = ".".join( + m.group(1) + for m in current_object_re.finditer(word) + if m.end(1) + start < cursor_offset + ) if not s: return None return LinePart(start, start + len(s), s) From c21753bf74e29343fbfd08af7a36180936e99c27 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 25 May 2021 17:56:28 +0200 Subject: [PATCH 1299/1650] Mark regexes as "private" --- bpython/line.py | 66 +++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index e103b9d97..431f0de2d 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -11,7 +11,7 @@ from .lazyre import LazyReCompile LinePart = namedtuple("LinePart", ["start", "stop", "word"]) -current_word_re = LazyReCompile(r"(? Optional[LinePart]: @@ -19,7 +19,7 @@ def current_word(cursor_offset: int, line: str) -> Optional[LinePart]: start = cursor_offset end = cursor_offset word = None - for m in current_word_re.finditer(line): + for m in _current_word_re.finditer(line): if m.start(1) < cursor_offset <= m.end(1): start = m.start(1) end = m.end(1) @@ -29,29 +29,29 @@ def current_word(cursor_offset: int, line: str) -> Optional[LinePart]: return LinePart(start, end, word) -current_dict_key_re = LazyReCompile(r"""[\w_][\w0-9._]*\[([\w0-9._(), '"]*)""") +_current_dict_key_re = LazyReCompile(r"""[\w_][\w0-9._]*\[([\w0-9._(), '"]*)""") def current_dict_key(cursor_offset: int, line: str) -> Optional[LinePart]: """If in dictionary completion, return the current key""" - for m in current_dict_key_re.finditer(line): + for m in _current_dict_key_re.finditer(line): if m.start(1) <= cursor_offset <= m.end(1): return LinePart(m.start(1), m.end(1), m.group(1)) return None -current_dict_re = LazyReCompile(r"""([\w_][\w0-9._]*)\[([\w0-9._(), '"]*)""") +_current_dict_re = LazyReCompile(r"""([\w_][\w0-9._]*)\[([\w0-9._(), '"]*)""") def current_dict(cursor_offset: int, line: str) -> Optional[LinePart]: """If in dictionary completion, return the dict that should be used""" - for m in current_dict_re.finditer(line): + for m in _current_dict_re.finditer(line): if m.start(2) <= cursor_offset <= m.end(2): return LinePart(m.start(1), m.end(1), m.group(1)) return None -current_string_re = LazyReCompile( +_current_string_re = LazyReCompile( '''(?P(?:""")|"|(?:''\')|')(?:((?P.+?)(?P=open))|''' """(?P.+))""" ) @@ -63,14 +63,14 @@ def current_string(cursor_offset: int, line: str) -> Optional[LinePart]: Weaker than bpython.Repl's current_string, because that checks that a string is a string based on previous lines in the buffer.""" - for m in current_string_re.finditer(line): + for m in _current_string_re.finditer(line): i = 3 if m.group(3) else 4 if m.start(i) <= cursor_offset <= m.end(i): return LinePart(m.start(i), m.end(i), m.group(i)) return None -current_object_re = LazyReCompile(r"([\w_][\w0-9_]*)[.]") +_current_object_re = LazyReCompile(r"([\w_][\w0-9_]*)[.]") def current_object(cursor_offset: int, line: str) -> Optional[LinePart]: @@ -82,7 +82,7 @@ def current_object(cursor_offset: int, line: str) -> Optional[LinePart]: start, end, word = match s = ".".join( m.group(1) - for m in current_object_re.finditer(word) + for m in _current_object_re.finditer(word) if m.end(1) + start < cursor_offset ) if not s: @@ -90,7 +90,7 @@ def current_object(cursor_offset: int, line: str) -> Optional[LinePart]: return LinePart(start, start + len(s), s) -current_object_attribute_re = LazyReCompile(r"([\w_][\w0-9_]*)[.]?") +_current_object_attribute_re = LazyReCompile(r"([\w_][\w0-9_]*)[.]?") def current_object_attribute( @@ -102,7 +102,7 @@ def current_object_attribute( if match is None: return None start, end, word = match - matches = current_object_attribute_re.finditer(word) + matches = _current_object_attribute_re.finditer(word) next(matches) for m in matches: if m.start(1) + start <= cursor_offset <= m.end(1) + start: @@ -110,7 +110,7 @@ def current_object_attribute( return None -current_from_import_from_re = LazyReCompile( +_current_from_import_from_re = LazyReCompile( r"from +([\w0-9_.]*)(?:\s+import\s+([\w0-9_]+[,]?\s*)+)*" ) @@ -124,7 +124,7 @@ def current_from_import_from( parts of an import: from (module) import (name1, name2) """ # TODO allow for as's - for m in current_from_import_from_re.finditer(line): + for m in _current_from_import_from_re.finditer(line): if (m.start(1) < cursor_offset <= m.end(1)) or ( m.start(2) < cursor_offset <= m.end(2) ): @@ -132,9 +132,11 @@ def current_from_import_from( return None -current_from_import_import_re_1 = LazyReCompile(r"from\s+([\w0-9_.]*)\s+import") -current_from_import_import_re_2 = LazyReCompile(r"([\w0-9_]+)") -current_from_import_import_re_3 = LazyReCompile(r", *([\w0-9_]*)") +_current_from_import_import_re_1 = LazyReCompile( + r"from\s+([\w0-9_.]*)\s+import" +) +_current_from_import_import_re_2 = LazyReCompile(r"([\w0-9_]+)") +_current_from_import_import_re_3 = LazyReCompile(r", *([\w0-9_]*)") def current_from_import_import( @@ -144,15 +146,15 @@ def current_from_import_import( returns None if cursor not in or just after one of these words """ - baseline = current_from_import_import_re_1.search(line) + baseline = _current_from_import_import_re_1.search(line) if baseline is None: return None - match1 = current_from_import_import_re_2.search(line[baseline.end() :]) + match1 = _current_from_import_import_re_2.search(line[baseline.end() :]) if match1 is None: return None for m in chain( (match1,), - current_from_import_import_re_3.finditer(line[baseline.end() :]), + _current_from_import_import_re_3.finditer(line[baseline.end() :]), ): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) @@ -161,21 +163,21 @@ def current_from_import_import( return None -current_import_re_1 = LazyReCompile(r"import") -current_import_re_2 = LazyReCompile(r"([\w0-9_.]+)") -current_import_re_3 = LazyReCompile(r"[,][ ]*([\w0-9_.]*)") +_current_import_re_1 = LazyReCompile(r"import") +_current_import_re_2 = LazyReCompile(r"([\w0-9_.]+)") +_current_import_re_3 = LazyReCompile(r"[,][ ]*([\w0-9_.]*)") def current_import(cursor_offset: int, line: str) -> Optional[LinePart]: # TODO allow for multiple as's - baseline = current_import_re_1.search(line) + baseline = _current_import_re_1.search(line) if baseline is None: return None - match1 = current_import_re_2.search(line[baseline.end() :]) + match1 = _current_import_re_2.search(line[baseline.end() :]) if match1 is None: return None for m in chain( - (match1,), current_import_re_3.finditer(line[baseline.end() :]) + (match1,), _current_import_re_3.finditer(line[baseline.end() :]) ): start = baseline.end() + m.start(1) end = baseline.end() + m.end(1) @@ -184,25 +186,25 @@ def current_import(cursor_offset: int, line: str) -> Optional[LinePart]: return None -current_method_definition_name_re = LazyReCompile(r"def\s+([a-zA-Z_][\w]*)") +_current_method_definition_name_re = LazyReCompile(r"def\s+([a-zA-Z_][\w]*)") def current_method_definition_name( cursor_offset: int, line: str ) -> Optional[LinePart]: """The name of a method being defined""" - for m in current_method_definition_name_re.finditer(line): + for m in _current_method_definition_name_re.finditer(line): if m.start(1) <= cursor_offset <= m.end(1): return LinePart(m.start(1), m.end(1), m.group(1)) return None -current_single_word_re = LazyReCompile(r"(? Optional[LinePart]: """the un-dotted word just before or under the cursor""" - for m in current_single_word_re.finditer(line): + for m in _current_single_word_re.finditer(line): if m.start(1) <= cursor_offset <= m.end(1): return LinePart(m.start(1), m.end(1), m.group(1)) return None @@ -221,7 +223,7 @@ def current_dotted_attribute( return None -current_expression_attribute_re = LazyReCompile( +_current_expression_attribute_re = LazyReCompile( r"[.]\s*((?:[\w_][\w0-9_]*)|(?:))" ) @@ -231,7 +233,7 @@ def current_expression_attribute( ) -> Optional[LinePart]: """If after a dot, the attribute being completed""" # TODO replace with more general current_expression_attribute - for m in current_expression_attribute_re.finditer(line): + for m in _current_expression_attribute_re.finditer(line): if m.start(1) <= cursor_offset <= m.end(1): return LinePart(m.start(1), m.end(1), m.group(1)) return None From 90ab512547fd154fbc0cdd7b7c57970ddb25d2b8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 30 May 2021 22:59:59 +0200 Subject: [PATCH 1300/1650] Replace namedtuple with typing.NamedTuple --- bpython/line.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 431f0de2d..7ced3bf1d 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -5,12 +5,17 @@ word.""" from itertools import chain -from collections import namedtuple -from typing import Optional +from typing import Optional, NamedTuple from .lazyre import LazyReCompile -LinePart = namedtuple("LinePart", ["start", "stop", "word"]) + +class LinePart(NamedTuple): + start: int + stop: int + word: str + + _current_word_re = LazyReCompile(r"(? Date: Sun, 30 May 2021 23:01:39 +0200 Subject: [PATCH 1301/1650] Fix name of LinePart member --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index f4424f7c8..975a2622c 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -244,7 +244,7 @@ def substitute(self, cursor_offset, line, match): """Returns a cursor offset and line with match swapped in""" lpart = self.locate(cursor_offset, line) offset = lpart.start + len(match) - changed_line = line[: lpart.start] + match + line[lpart.end :] + changed_line = line[: lpart.start] + match + line[lpart.stop :] return offset, changed_line @property From 7f325dfd45ee60765f4346054fd23de29da31044 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 1 Aug 2021 17:13:23 +0200 Subject: [PATCH 1302/1650] Handle OSError from stat (fixes #902) --- bpython/importcompletion.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 14262db4d..00bf99f31 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -212,7 +212,10 @@ def find_modules(self, path: Path) -> Generator[str, None, None]: else: if is_package: path_real = Path(pathname).resolve() - stat = path_real.stat() + try: + stat = path_real.stat() + except OSError: + continue if (stat.st_dev, stat.st_ino) not in self.paths: self.paths.add((stat.st_dev, stat.st_ino)) for subname in self.find_modules(path_real): From 5eb31eb64ea02b9fdfae5a468d3f01b574352dd4 Mon Sep 17 00:00:00 2001 From: Geoff Maciolek Date: Thu, 5 Aug 2021 10:18:26 -0400 Subject: [PATCH 1303/1650] Grammatical & formatting improvements: README.rst A bit of editing to move closer to standard U.S. English grammar & conventions. (Also, wrapping some terms in backticks for clarity & consistency.) --- README.rst | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index 8a26442be..539d036bd 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ few ideas in a practical, useful, and lightweight manner. bpython is a great replacement to any occasion where you would normally use the vanilla Python interpreter - testing out solutions to people's problems on IRC, quickly testing a method of doing something without creating a temporary file, -etc.. +etc. You can find more about bpython - including `full documentation`_ - at our `homepage`_. @@ -59,14 +59,14 @@ Python. Installation via OS Package Manager ----------------------------------- -The majority of operating system of desktop computers comes with a package -manager system, if you are any user of them, you can install ``bpython`` -using the package manager. +The majority of desktop computer operating systems come with package management +systems. If you use one of these OSes, you can install ``bpython`` using the +package manager. Ubuntu/Debian ~~~~~~~~~~~~~ -Ubuntu/Debian family Linux users and install bpython using the apt package manager, using the -command with sudo privilege: +Ubuntu/Debian family Linux users can install ``bpython`` using the ``apt`` +package manager, using the command with ``sudo`` privileges: .. code-block:: bash @@ -80,7 +80,7 @@ In case you are using an older version, run Arch Linux ~~~~~~~~~~ -Arch linux uses pacman as the default package manager, and you can use it to install bpython: +Arch Linux uses ``pacman`` as the default package manager; you can use it to install ``bpython``: .. code-block:: bash @@ -88,7 +88,7 @@ Arch linux uses pacman as the default package manager, and you can use it to ins Fedora ~~~~~~~~~~ -Fedora users can install bpython directly from the command line using ``dnf``. +Fedora users can install ``bpython`` directly from the command line using ``dnf``. .. code-block:: bash @@ -99,8 +99,8 @@ Windows **Caveats:** As ``bpython`` makes use of the ncurses library of \*nix-family operating systems, bpython on Windows is not officially supported and tested. -However, you can still use bpython on Windows using a somewhat work around. Briefly, you should install -these two packages using pip: +However, you may still use bpython on Windows using a workaround. In brief, you should install +these two packages using ``pip``: .. code-block:: bash @@ -130,9 +130,8 @@ Features & Examples type, and colours appropriately. * Expected parameter list. As in a lot of modern IDEs, bpython will attempt to - display a list of parameters for any function you call. The inspect module is - tried first, which works with any Python function, and then pydoc if that - fails. + display a list of parameters for any function you call. The inspect module (which + works with any Python function) is tried first, and then pydoc if that fails. * Rewind. This isn't called "Undo" because it would be misleading, but "Rewind" is probably as bad. The idea is that the code entered is kept in memory and From 98617816e59f2c1a0ad874c1a270e177dafde364 Mon Sep 17 00:00:00 2001 From: supremestdoggo <83146042+supremestdoggo@users.noreply.github.com> Date: Fri, 6 Aug 2021 21:42:02 -0400 Subject: [PATCH 1304/1650] Add type annotations to a few files --- bpython/_internal.py | 6 ++-- bpython/args.py | 7 ++-- bpython/autocomplete.py | 71 +++++++++++++++++++++-------------------- bpython/cli.py | 21 ++++++------ bpython/curtsies.py | 2 +- 5 files changed, 55 insertions(+), 52 deletions(-) diff --git a/bpython/_internal.py b/bpython/_internal.py index 4545862c4..35cd0bf80 100644 --- a/bpython/_internal.py +++ b/bpython/_internal.py @@ -8,20 +8,20 @@ class _Helper: - def __init__(self): + def __init__(self) -> None: if hasattr(pydoc.Helper, "output"): # See issue #228 self.helper = pydoc.Helper(sys.stdin, None) else: self.helper = pydoc.Helper(sys.stdin, sys.stdout) - def __repr__(self): + def __repr__(self) -> str: return ( "Type help() for interactive help, " "or help(object) for help about object." ) - def __call__(self, *args, **kwargs): + def __call__(self, *args, **kwargs) -> None: self.helper(*args, **kwargs) diff --git a/bpython/args.py b/bpython/args.py index e3095e17c..7895d84ad 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -26,6 +26,7 @@ """ import argparse +from typing import Tuple import curtsies import cwcwidth import greenlet @@ -53,7 +54,7 @@ def error(self, msg): raise ArgumentParserFailed() -def version_banner(base="bpython"): +def version_banner(base="bpython") -> str: return _("{} version {} on top of Python {} {}").format( base, __version__, @@ -62,11 +63,11 @@ def version_banner(base="bpython"): ) -def copyright_banner(): +def copyright_banner() -> str: return _("{} See AUTHORS.rst for details.").format(__copyright__) -def parse(args, extras=None, ignore_stdin=False): +def parse(args, extras=None, ignore_stdin=False) -> Tuple: """Receive an argument list - if None, use sys.argv - parse all args and take appropriate action. Also receive optional extra argument: this should be a tuple of (title, description, callback) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 975a2622c..0697e4906 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -33,6 +33,7 @@ import builtins from enum import Enum +from typing import Any, Dict, Iterator, List, Match, NoReturn, Set, Union from . import inspection from . import line as lineparts from .line import LinePart @@ -48,7 +49,7 @@ class AutocompleteModes(Enum): FUZZY = "fuzzy" @classmethod - def from_string(cls, value): + def from_string(cls, value) -> Union[Any, None]: if value.upper() in cls.__members__: return cls.__members__[value.upper()] return None @@ -161,11 +162,11 @@ def from_string(cls, value): KEYWORDS = frozenset(keyword.kwlist) -def after_last_dot(name): +def after_last_dot(name: str) -> str: return name.rstrip(".").rsplit(".")[-1] -def few_enough_underscores(current, match): +def few_enough_underscores(current, match) -> bool: """Returns whether match should be shown based on current if current is _, True if match starts with 0 or 1 underscore @@ -179,19 +180,19 @@ def few_enough_underscores(current, match): return not match.startswith("_") -def method_match_none(word, size, text): +def method_match_none(word, size, text) -> False: return False -def method_match_simple(word, size, text): +def method_match_simple(word, size, text) -> bool: return word[:size] == text -def method_match_substring(word, size, text): +def method_match_substring(word, size, text) -> bool: return text in word -def method_match_fuzzy(word, size, text): +def method_match_fuzzy(word, size, text) -> Union[Match, None]: s = r".*%s.*" % ".*".join(list(text)) return re.search(s, word) @@ -207,11 +208,11 @@ def method_match_fuzzy(word, size, text): class BaseCompletionType: """Describes different completion types""" - def __init__(self, shown_before_tab=True, mode=AutocompleteModes.SIMPLE): + def __init__(self, shown_before_tab: bool=True, mode=AutocompleteModes.SIMPLE) -> None: self._shown_before_tab = shown_before_tab self.method_match = MODES_MAP[mode] - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> NoReturn: """Returns a list of possible matches given a line and cursor, or None if this completion type isn't applicable. @@ -229,7 +230,7 @@ def matches(self, cursor_offset, line, **kwargs): """ raise NotImplementedError - def locate(self, cursor_offset, line): + def locate(self, cursor_offset, line) -> NoReturn: """Returns a Linepart namedtuple instance or None given cursor and line A Linepart namedtuple contains a start, stop, and word. None is @@ -240,7 +241,7 @@ def locate(self, cursor_offset, line): def format(self, word): return word - def substitute(self, cursor_offset, line, match): + def substitute(self, cursor_offset, line, match) -> NoReturn: """Returns a cursor offset and line with match swapped in""" lpart = self.locate(cursor_offset, line) offset = lpart.start + len(match) @@ -248,7 +249,7 @@ def substitute(self, cursor_offset, line, match): return offset, changed_line @property - def shown_before_tab(self): + def shown_before_tab(self) -> bool: """Whether suggestions should be shown before the user hits tab, or only once that has happened.""" return self._shown_before_tab @@ -257,7 +258,7 @@ def shown_before_tab(self): class CumulativeCompleter(BaseCompletionType): """Returns combined matches from several completers""" - def __init__(self, completers, mode=AutocompleteModes.SIMPLE): + def __init__(self, completers, mode=AutocompleteModes.SIMPLE) -> None: if not completers: raise ValueError( "CumulativeCompleter requires at least one completer" @@ -266,7 +267,7 @@ def __init__(self, completers, mode=AutocompleteModes.SIMPLE): super().__init__(True, mode) - def locate(self, current_offset, line): + def locate(self, current_offset, line) -> Union[None, NoReturn]: for completer in self._completers: return_value = completer.locate(current_offset, line) if return_value is not None: @@ -275,7 +276,7 @@ def locate(self, current_offset, line): def format(self, word): return self._completers[0].format(word) - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: return_value = None all_matches = set() for completer in self._completers: @@ -308,10 +309,10 @@ class FilenameCompletion(BaseCompletionType): def __init__(self, mode=AutocompleteModes.SIMPLE): super().__init__(False, mode) - def safe_glob(self, pathname): + def safe_glob(self, pathname) -> Iterator: return glob.iglob(glob.escape(pathname) + "*") - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> Union[None, set]: cs = lineparts.current_string(cursor_offset, line) if cs is None: return None @@ -341,7 +342,7 @@ class AttrCompletion(BaseCompletionType): attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: if "locals_" not in kwargs: return None locals_ = kwargs["locals_"] @@ -377,7 +378,7 @@ def locate(self, current_offset, line): def format(self, word): return after_last_dot(word) - def attr_matches(self, text, namespace): + def attr_matches(self, text, namespace) -> List: """Taken from rlcompleter.py and bent to my will.""" m = self.attr_matches_re.match(text) @@ -396,7 +397,7 @@ def attr_matches(self, text, namespace): matches = self.attr_lookup(obj, expr, attr) return matches - def attr_lookup(self, obj, expr, attr): + def attr_lookup(self, obj, expr, attr) -> List: """Second half of attr_matches.""" words = self.list_attributes(obj) if inspection.hasattr_safe(obj, "__class__"): @@ -416,7 +417,7 @@ def attr_lookup(self, obj, expr, attr): matches.append(f"{expr}.{word}") return matches - def list_attributes(self, obj): + def list_attributes(self, obj) -> List[str]: # TODO: re-implement dir using getattr_static to avoid using # AttrCleaner here? with inspection.AttrCleaner(obj): @@ -424,7 +425,7 @@ def list_attributes(self, obj): class DictKeyCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: if "locals_" not in kwargs: return None locals_ = kwargs["locals_"] @@ -445,7 +446,7 @@ def matches(self, cursor_offset, line, **kwargs): else: return None - def locate(self, current_offset, line): + def locate(self, current_offset, line) -> Union[LinePart, None]: return lineparts.current_dict_key(current_offset, line) def format(self, match): @@ -453,7 +454,7 @@ def format(self, match): class MagicMethodCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: if "current_block" not in kwargs: return None current_block = kwargs["current_block"] @@ -465,12 +466,12 @@ def matches(self, cursor_offset, line, **kwargs): return None return {name for name in MAGIC_METHODS if name.startswith(r.word)} - def locate(self, current_offset, line): + def locate(self, current_offset, line) -> Union[LinePart, None]: return lineparts.current_method_definition_name(current_offset, line) class GlobalCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> Union[Set, None]: """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace that match. @@ -500,12 +501,12 @@ def matches(self, cursor_offset, line, **kwargs): matches.add(_callable_postfix(val, word)) return matches if matches else None - def locate(self, current_offset, line): + def locate(self, current_offset, line) -> Union[LinePart, None]: return lineparts.current_single_word(current_offset, line) class ParameterNameCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: if "argspec" not in kwargs: return None argspec = kwargs["argspec"] @@ -526,16 +527,16 @@ def matches(self, cursor_offset, line, **kwargs): ) return matches if matches else None - def locate(self, current_offset, line): + def locate(self, current_offset, line) -> Union[LinePart, None]: return lineparts.current_word(current_offset, line) class ExpressionAttributeCompletion(AttrCompletion): # could replace attr completion as a more general case with some work - def locate(self, current_offset, line): + def locate(self, current_offset, line) -> Union[LinePart, None]: return lineparts.current_expression_attribute(current_offset, line) - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> Union[Set, Dict, None]: if "locals_" not in kwargs: return None locals_ = kwargs["locals_"] @@ -560,14 +561,14 @@ def matches(self, cursor_offset, line, **kwargs): except ImportError: class MultilineJediCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> None: return None else: class JediCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: if "history" not in kwargs: return None history = kwargs["history"] @@ -607,13 +608,13 @@ def matches(self, cursor_offset, line, **kwargs): # case-sensitive matches only return {m for m in matches if m.startswith(first_letter)} - def locate(self, cursor_offset, line): + def locate(self, cursor_offset, line) -> LinePart: start = self._orig_start end = cursor_offset return LinePart(start, end, line[start:end]) class MultilineJediCompletion(JediCompletion): - def matches(self, cursor_offset, line, **kwargs): + def matches(self, cursor_offset, line, **kwargs) -> Union[Dict, None]: if "current_block" not in kwargs or "history" not in kwargs: return None current_block = kwargs["current_block"] diff --git a/bpython/cli.py b/bpython/cli.py index 8add9f743..baa2059da 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -50,6 +50,7 @@ import struct import sys import time +from typing import Iterator, NoReturn import unicodedata from dataclasses import dataclass @@ -97,7 +98,7 @@ class ShowListState: wl: int = 0 -def calculate_screen_lines(tokens, width, cursor=0): +def calculate_screen_lines(tokens, width, cursor=0) -> int: """Given a stream of tokens and a screen width plus an optional initial cursor position, return the amount of needed lines on the screen.""" @@ -130,31 +131,31 @@ class FakeStream: provided.""" def __init__(self, interface, get_dest): - self.encoding = getpreferredencoding() + self.encoding: str = getpreferredencoding() self.interface = interface self.get_dest = get_dest @forward_if_not_current - def write(self, s): + def write(self, s) -> None: self.interface.write(s) @forward_if_not_current - def writelines(self, l): + def writelines(self, l) -> None: for s in l: self.write(s) - def isatty(self): + def isatty(self) -> True: # some third party (amongst them mercurial) depend on this return True - def flush(self): + def flush(self) -> None: self.interface.flush() class FakeStdin: """Provide a fake stdin type for things like raw_input() etc.""" - def __init__(self, interface): + def __init__(self, interface) -> None: """Take the curses Repl on init and assume it provides a get_key method which, fortunately, it does.""" @@ -162,19 +163,19 @@ def __init__(self, interface): self.interface = interface self.buffer = list() - def __iter__(self): + def __iter__(self) -> Iterator: return iter(self.readlines()) def flush(self): """Flush the internal buffer. This is a no-op. Flushing stdin doesn't make any sense anyway.""" - def write(self, value): + def write(self, value) -> NoReturn: # XXX IPython expects sys.stdin.write to exist, there will no doubt be # others, so here's a hack to keep them happy raise OSError(errno.EBADF, "sys.stdin is read-only") - def isatty(self): + def isatty(self) -> True: return True def readline(self, size=-1): diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 2bd348335..139bbae36 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -20,7 +20,7 @@ class FullCurtsiesRepl(BaseRepl): - def __init__(self, config, locals_, banner, interp=None): + def __init__(self, config, locals_, banner, interp=None) -> None: self.input_generator = curtsies.input.Input( keynames="curtsies", sigint_event=True, paste_threshold=None ) From f7d8b775d7f82db0196dad0a96f1b0c9b4c544ee Mon Sep 17 00:00:00 2001 From: supremestdoggo <83146042+supremestdoggo@users.noreply.github.com> Date: Thu, 12 Aug 2021 19:45:18 -0400 Subject: [PATCH 1305/1650] Reformat via black --- bpython/autocomplete.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 0697e4906..d8f04f64a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -208,7 +208,9 @@ def method_match_fuzzy(word, size, text) -> Union[Match, None]: class BaseCompletionType: """Describes different completion types""" - def __init__(self, shown_before_tab: bool=True, mode=AutocompleteModes.SIMPLE) -> None: + def __init__( + self, shown_before_tab: bool = True, mode=AutocompleteModes.SIMPLE + ) -> None: self._shown_before_tab = shown_before_tab self.method_match = MODES_MAP[mode] From 87a85dff224b50f28ac7558f728afe9902f67457 Mon Sep 17 00:00:00 2001 From: Ulises Ojeda Ogando Date: Sat, 25 Sep 2021 10:06:02 +0200 Subject: [PATCH 1306/1650] checking lines array length --- bpython/curtsiesfrontend/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index f38304e9a..30fbbb14e 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1004,9 +1004,9 @@ def send_session_to_external_editor(self, filename=None): ) return lines = text.split("\n") - if not lines[-1].strip(): + if len(lines) and not lines[-1].strip(): lines.pop() # strip last line if empty - if lines[-1].startswith("### "): + if len(lines) and lines[-1].startswith("### "): current_line = lines[-1][4:] else: current_line = "" From b66a29fde838f98a39c08ce1b94f6cc8b8c3e86d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sat, 25 Sep 2021 10:08:36 -0700 Subject: [PATCH 1307/1650] Use kwarg for request_undo_callback to fix undo. --- bpython/curtsies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 139bbae36..10fab77f4 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -74,7 +74,7 @@ def interrupting_refresh(self): return self._interrupting_refresh_callback() def request_undo(self, n=1): - return self._request_undo_callback(n) + return self._request_undo_callback(n=n) def get_term_hw(self): return self.window.get_term_hw() From e69cfe3eb164ad0ddcc87c6998442b42596fba49 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 1 Oct 2021 22:21:26 +0200 Subject: [PATCH 1308/1650] Behave like python3 when executing files with -i (fixes #919) --- bpython/args.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 7895d84ad..79ddcc679 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -216,11 +216,17 @@ def callback(group): def exec_code(interpreter, args): """ - Helper to execute code in a given interpreter. args should be a [faked] - sys.argv + Helper to execute code in a given interpreter, e.g. to implement the behavior of python3 [-i] file.py + + args should be a [faked] sys.argv. """ - with open(args[0]) as sourcefile: - source = sourcefile.read() + try: + with open(args[0]) as sourcefile: + source = sourcefile.read() + except OSError as e: + # print an error and exit (if -i is specified the calling code will continue) + print(f"bpython: can't open file '{args[0]}: {e}", file=sys.stderr) + raise SystemExit(e.errno) old_argv, sys.argv = sys.argv, args sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) spec = importlib.util.spec_from_loader("__console__", loader=None) From 18f6901b8bb6b530112381003b70f10792be616e Mon Sep 17 00:00:00 2001 From: arian-deimling <71411328+arian-deimling@users.noreply.github.com> Date: Wed, 6 Oct 2021 12:17:20 -0400 Subject: [PATCH 1309/1650] Changed dict key-matching regex to capture any valid dict key (#920) * Changed dict key-matching regex to capture any valid dict key * dict key matching regex no longer matches beyond the end of a key * Updated regex to handle str, bytes, int, float, tuple dict keys * Added comments to regex using re.VERBOSE flag * added test case for string dict key being typed inside [] --- bpython/line.py | 49 ++++++++++++++++++++++++++-- bpython/test/test_line_properties.py | 14 ++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 7ced3bf1d..b98302dd2 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -4,6 +4,8 @@ Python code, and return None, or a tuple of the start index, end index, and the word.""" +import re + from itertools import chain from typing import Optional, NamedTuple @@ -34,7 +36,41 @@ def current_word(cursor_offset: int, line: str) -> Optional[LinePart]: return LinePart(start, end, word) -_current_dict_key_re = LazyReCompile(r"""[\w_][\w0-9._]*\[([\w0-9._(), '"]*)""") +# pieces of regex to match repr() of several hashable built-in types +_match_all_dict_keys = r"""[^\]]*""" + +# https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals +_match_single_quote_str_bytes = r""" + # bytes repr() begins with `b` character; bytes and str begin with `'` + b?' + # match escape sequence; this handles `\'` in the string repr() + (?:\\['"nabfrtvxuU\\]| + # or match any non-`\` and non-single-quote character (most of the string) + [^'\\])* + # matches hanging `\` or ending `'` if one is present + [\\']? +""" + +# bytes and str repr() only uses double quotes if the string contains 1 or more +# `'` character and exactly 0 `"` characters +_match_double_quote_str_bytes = r""" + # bytes repr() begins with `b` character + b?" + # string continues until a `"` character is reached + [^"]* + # end matching at closing double-quote if one is present + "?""" + +# match valid identifier name followed by `[` character +_match_dict_before_key = r"""[\w_][\w0-9._]*\[""" + +_current_dict_key_re = LazyReCompile( + f"{_match_dict_before_key}((?:" + f"{_match_single_quote_str_bytes}|" + f"{_match_double_quote_str_bytes}|" + f"{_match_all_dict_keys}|)*)", + re.VERBOSE, +) def current_dict_key(cursor_offset: int, line: str) -> Optional[LinePart]: @@ -45,7 +81,16 @@ def current_dict_key(cursor_offset: int, line: str) -> Optional[LinePart]: return None -_current_dict_re = LazyReCompile(r"""([\w_][\w0-9._]*)\[([\w0-9._(), '"]*)""") +# capture valid identifier name if followed by `[` character +_capture_dict_name = r"""([\w_][\w0-9._]*)\[""" + +_current_dict_re = LazyReCompile( + f"{_capture_dict_name}((?:" + f"{_match_single_quote_str_bytes}|" + f"{_match_double_quote_str_bytes}|" + f"{_match_all_dict_keys}|)*)", + re.VERBOSE, +) def current_dict(cursor_offset: int, line: str) -> Optional[LinePart]: diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index fe1b0813e..592a61765 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -178,10 +178,24 @@ def test_simple(self): self.assertAccess("asdf[<(>|]") self.assertAccess("asdf[<(1>|]") self.assertAccess("asdf[<(1,>|]") + self.assertAccess("asdf[<(1,)>|]") self.assertAccess("asdf[<(1, >|]") self.assertAccess("asdf[<(1, 2)>|]") # TODO self.assertAccess('d[d[<12|>') self.assertAccess("d[<'a>|") + self.assertAccess("object.dict['a'bcd'], object.dict[<'abc>|") + self.assertAccess("object.dict[<'a'bcd'>|], object.dict['abc") + self.assertAccess(r"object.dict[<'a\'\\\"\n\\'>|") + self.assertAccess("object.dict[<\"abc'>|") + self.assertAccess("object.dict[<(1, 'apple', 2.134>|]") + self.assertAccess("object.dict[<(1, 'apple', 2.134)>|]") + self.assertAccess("object.dict[<-1000>|") + self.assertAccess("object.dict[<-0.23948>|") + self.assertAccess("object.dict[<'\U0001ffff>|") + self.assertAccess(r"object.dict[<'a\'\\\"\n\\'>|]") + self.assertAccess(r"object.dict[<'a\'\\\"\n\\|[[]'>") + self.assertAccess('object.dict[<"a]bc[|]">]') + self.assertAccess("object.dict[<'abcd[]>|") class TestCurrentDict(LineTestCase): From 5ab308763f72371f80cbe33f93bbaaa70620c17d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 6 Oct 2021 18:45:08 -0700 Subject: [PATCH 1310/1650] disable some tests for pypy --- bpython/test/test_inspection.py | 4 ++++ bpython/test/test_interpreter.py | 2 +- bpython/test/test_repl.py | 7 +++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 7c04c521e..fecb848b3 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -1,4 +1,5 @@ import os +import sys import unittest from bpython import inspection @@ -6,6 +7,8 @@ from bpython.test.fodder import encoding_latin1 from bpython.test.fodder import encoding_utf8 +pypy = "PyPy" in sys.version + try: import numpy except ImportError: @@ -118,6 +121,7 @@ def test_get_source_file(self): ) self.assertEqual(encoding, "utf-8") + @unittest.skipIf(pypy, "pypy builtin signatures aren't complete") def test_getfuncprops_print(self): props = inspection.getfuncprops("print", print) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 9b93672c0..36e82c0eb 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -53,7 +53,7 @@ def test_syntaxerror(self): + green('""') + ", line " + bold(magenta("1")) - + "\n 1.1.1.1\n ^\n" + + "\n 1.1.1.1\n ^\n" + bold(red("SyntaxError")) + ": " + cyan("invalid syntax") diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 12275e227..65a2fb813 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -157,7 +157,8 @@ def set_input_line(self, line): def test_func_name(self): for (line, expected_name) in [ ("spam(", "spam"), - ("spam(map([]", "map"), + # map pydoc has no signature in pypy + ("spam(any([]", "any") if pypy else ("spam(map([]", "map"), ("spam((), ", "spam"), ]: self.set_input_line(line) @@ -167,7 +168,8 @@ def test_func_name(self): def test_func_name_method_issue_479(self): for (line, expected_name) in [ ("o.spam(", "spam"), - ("o.spam(map([]", "map"), + # map pydoc has no signature in pypy + ("o.spam(any([]", "any") if pypy else ("o.spam(map([]", "map"), ("o.spam((), ", "spam"), ]: self.set_input_line(line) @@ -200,6 +202,7 @@ def test_lambda_position(self): # Argument position self.assertEqual(self.repl.arg_pos, 1) + @unittest.skipIf(pypy, "range pydoc has no signature in pypy") def test_issue127(self): self.set_input_line("x=range(") self.assertTrue(self.repl.get_args()) From 78ead4c83d7c47c35ab075dd08114f51c24a709d Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 6 Oct 2021 19:59:38 -0700 Subject: [PATCH 1311/1650] Test Python 3.10 (#924) * Test Python 3.10 * Update tests for Python 3.10 --- .github/workflows/build.yaml | 2 +- bpython/test/test_curtsies_painting.py | 8 ++++++++ bpython/test/test_interpreter.py | 14 +++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b877487a7..dc1fc9ed2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,7 +13,7 @@ jobs: continue-on-error: ${{ matrix.python-version == 'pypy3' }} strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9, pypy3] + python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] steps: - uses: actions/checkout@v2 with: diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index adc63f155..9f98bf066 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -139,6 +139,14 @@ def test_completion(self): "└──────────────────────────────┘", "Welcome to bpython! Press f", ] + if sys.version_info[:2] < (3, 10) + else [ + ">>> an", + "┌──────────────────────────────┐", + "│ and anext( any( │", + "└──────────────────────────────┘", + "Welcome to bpython! Press f", + ] ) self.assert_paint_ignoring_formatting(screen, (0, 4)) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 36e82c0eb..ca64de77b 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -35,7 +35,19 @@ def test_syntaxerror(self): i.runsource("1.1.1.1") - if sys.version_info[:2] >= (3, 8): + if sys.version_info[:2] >= (3, 10): + expected = ( + " File " + + green('""') + + ", line " + + bold(magenta("1")) + + "\n 1.1.1.1\n ^^^^^\n" + + bold(red("SyntaxError")) + + ": " + + cyan("invalid syntax. Perhaps you forgot a comma?") + + "\n" + ) + elif (3, 8) <= sys.version_info[:2] <= (3, 9): expected = ( " File " + green('""') From 2308a61669bd90bb18cfefe31afa7cee6518867e Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 6 Oct 2021 22:45:44 -0700 Subject: [PATCH 1312/1650] Fix #909 Static types would have helped! I'll add some. --- bpython/curtsiesfrontend/repl.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 30fbbb14e..21b606e67 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -367,7 +367,7 @@ def __init__( self._current_line = "" # current line of output - stdout and stdin go here - self.current_stdouterr_line = "" + self.current_stdouterr_line = "" # Union[str, FmtStr] # this is every line that's been displayed (input and output) # as with formatting applied. Logical lines that exceeded the terminal width @@ -1582,8 +1582,18 @@ def move_screen_up(current_line_start_row): current_line_height = current_line_end_row - current_line_start_row if self.stdin.has_focus: + logger.debug( + "stdouterr when self.stdin has focus: %r %r", + type(self.current_stdouterr_line), + self.current_stdouterr_line, + ) + stdouterr_width = ( + self.current_stdouterr_line.width + if isinstance(self.current_stdouterr_line, FmtStr) + else wcswidth(self.current_stdouterr_line) + ) cursor_row, cursor_column = divmod( - wcswidth(self.current_stdouterr_line) + stdouterr_width + wcswidth( self.stdin.current_line, max(0, self.stdin.cursor_offset) ), From d9305d7a88beb8536c798767df5ddecfb36d9780 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 7 Oct 2021 00:50:14 -0700 Subject: [PATCH 1313/1650] Mypy types for completion (#928) * Mypy types for completion We're not checking these in CI nor are we providing a config file yet. I used mypy 0.910 in Python 3.6 run directly on bpython/autocomplete.py and still had 3 errors after these changes. --- bpython/autocomplete.py | 47 +++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index d8f04f64a..3ec2a4578 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -33,7 +33,7 @@ import builtins from enum import Enum -from typing import Any, Dict, Iterator, List, Match, NoReturn, Set, Union +from typing import Any, Dict, Iterator, List, Match, NoReturn, Set, Union, Tuple from . import inspection from . import line as lineparts from .line import LinePart @@ -180,7 +180,7 @@ def few_enough_underscores(current, match) -> bool: return not match.startswith("_") -def method_match_none(word, size, text) -> False: +def method_match_none(word, size, text) -> bool: return False @@ -214,7 +214,10 @@ def __init__( self._shown_before_tab = shown_before_tab self.method_match = MODES_MAP[mode] - def matches(self, cursor_offset, line, **kwargs) -> NoReturn: + @abc.abstractmethod + def matches( + self, cursor_offset: int, line: str, **kwargs + ) -> Union[Set[str], None]: """Returns a list of possible matches given a line and cursor, or None if this completion type isn't applicable. @@ -232,7 +235,8 @@ def matches(self, cursor_offset, line, **kwargs) -> NoReturn: """ raise NotImplementedError - def locate(self, cursor_offset, line) -> NoReturn: + @abc.abstractmethod + def locate(self, cursor_offset: int, line: str) -> Union[LinePart, None]: """Returns a Linepart namedtuple instance or None given cursor and line A Linepart namedtuple contains a start, stop, and word. None is @@ -243,9 +247,10 @@ def locate(self, cursor_offset, line) -> NoReturn: def format(self, word): return word - def substitute(self, cursor_offset, line, match) -> NoReturn: + def substitute(self, cursor_offset, line, match) -> Tuple[int, str]: """Returns a cursor offset and line with match swapped in""" lpart = self.locate(cursor_offset, line) + assert lpart offset = lpart.start + len(match) changed_line = line[: lpart.start] + match + line[lpart.stop :] return offset, changed_line @@ -269,16 +274,19 @@ def __init__(self, completers, mode=AutocompleteModes.SIMPLE) -> None: super().__init__(True, mode) - def locate(self, current_offset, line) -> Union[None, NoReturn]: + def locate(self, current_offset: int, line: str) -> Union[None, NoReturn]: for completer in self._completers: return_value = completer.locate(current_offset, line) if return_value is not None: return return_value + return None def format(self, word): return self._completers[0].format(word) - def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: + def matches( + self, cursor_offset: int, line: str, **kwargs + ) -> Union[None, Set]: return_value = None all_matches = set() for completer in self._completers: @@ -344,7 +352,7 @@ class AttrCompletion(BaseCompletionType): attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") - def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: + def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: if "locals_" not in kwargs: return None locals_ = kwargs["locals_"] @@ -427,7 +435,7 @@ def list_attributes(self, obj) -> List[str]: class DictKeyCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: + def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: if "locals_" not in kwargs: return None locals_ = kwargs["locals_"] @@ -435,7 +443,9 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: r = self.locate(cursor_offset, line) if r is None: return None - _, _, dexpr = lineparts.current_dict(cursor_offset, line) + curDictParts = lineparts.current_dict(cursor_offset, line) + assert curDictParts, "current_dict when .locate() truthy" + _, _, dexpr = curDictParts try: obj = safe_eval(dexpr, locals_) except EvaluationError: @@ -456,7 +466,7 @@ def format(self, match): class MagicMethodCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: + def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: if "current_block" not in kwargs: return None current_block = kwargs["current_block"] @@ -508,7 +518,7 @@ def locate(self, current_offset, line) -> Union[LinePart, None]: class ParameterNameCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: + def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: if "argspec" not in kwargs: return None argspec = kwargs["argspec"] @@ -538,7 +548,7 @@ class ExpressionAttributeCompletion(AttrCompletion): def locate(self, current_offset, line) -> Union[LinePart, None]: return lineparts.current_expression_attribute(current_offset, line) - def matches(self, cursor_offset, line, **kwargs) -> Union[Set, Dict, None]: + def matches(self, cursor_offset, line, **kwargs) -> Union[Set, None]: if "locals_" not in kwargs: return None locals_ = kwargs["locals_"] @@ -547,6 +557,7 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[Set, Dict, None]: locals_ = __main__.__dict__ attr = self.locate(cursor_offset, line) + assert attr, "locate was already truthy for the same call" try: obj = evaluate_current_expression(cursor_offset, line, locals_) @@ -570,7 +581,9 @@ def matches(self, cursor_offset, line, **kwargs) -> None: else: class JediCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: + _orig_start: Union[int, None] + + def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: if "history" not in kwargs: return None history = kwargs["history"] @@ -596,6 +609,7 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: else: self._orig_start = None return None + assert isinstance(self._orig_start, int) first_letter = line[self._orig_start : self._orig_start + 1] @@ -610,13 +624,14 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[None, Dict]: # case-sensitive matches only return {m for m in matches if m.startswith(first_letter)} - def locate(self, cursor_offset, line) -> LinePart: + def locate(self, cursor_offset: int, line: str) -> LinePart: + assert isinstance(self._orig_start, int) start = self._orig_start end = cursor_offset return LinePart(start, end, line[start:end]) class MultilineJediCompletion(JediCompletion): - def matches(self, cursor_offset, line, **kwargs) -> Union[Dict, None]: + def matches(self, cursor_offset, line, **kwargs) -> Union[Set, None]: if "current_block" not in kwargs or "history" not in kwargs: return None current_block = kwargs["current_block"] From 76d2ae6d3f424b8a36abbbc9f01c4b7b6287f94a Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 8 Oct 2021 15:02:37 -0700 Subject: [PATCH 1314/1650] Remove call of nonexistent method. --- bpython/curtsiesfrontend/repl.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 21b606e67..58aa6127d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -116,8 +116,6 @@ def process_event(self, e): self.current_line = "" self.cursor_offset = 0 self.repl.run_code_and_maybe_finish() - elif e in ("",): - self.get_last_word() elif e in ("",): pass elif e in ("",): From 4ffa123951e46259388e8f13b66164630bc33135 Mon Sep 17 00:00:00 2001 From: arian-deimling <71411328+arian-deimling@users.noreply.github.com> Date: Fri, 8 Oct 2021 18:23:57 -0400 Subject: [PATCH 1315/1650] Fixed typo (#930) --- doc/sphinx/source/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 84fb2da55..76d3b9402 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -60,7 +60,7 @@ Next install your development copy of bpython and its dependencies: .. code-block:: bash - $ sudp apt install python3-greenlet python3-pygments python3-requests + $ sudo apt install python3-greenlet python3-pygments python3-requests $ sudo apt install python3-watchdog python3-urwid $ sudo apt install python3-sphinx python3-pytest From 4d744ca4c43e86c89a47b8ff1d031781eff7ec0c Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 10 Oct 2021 09:32:59 -0700 Subject: [PATCH 1316/1650] Mypy types for autocomplete.py, args.py, config.py and curtsies.py (#929) --- .github/workflows/lint.yaml | 17 +++ bpython/__init__.py | 2 +- bpython/args.py | 31 +++- bpython/autocomplete.py | 189 ++++++++++++++++-------- bpython/cli.py | 8 +- bpython/config.py | 4 + bpython/curtsies.py | 107 ++++++++++---- bpython/curtsiesfrontend/filewatch.py | 2 +- bpython/curtsiesfrontend/interaction.py | 2 +- bpython/curtsiesfrontend/interpreter.py | 5 +- bpython/curtsiesfrontend/repl.py | 78 +++++----- bpython/inspection.py | 6 +- bpython/lazyre.py | 10 +- bpython/patch_linecache.py | 2 +- bpython/repl.py | 12 +- bpython/simpleeval.py | 7 +- bpython/test/test_crashers.py | 4 +- bpython/test/test_curtsies_repl.py | 5 +- bpython/test/test_inspection.py | 4 +- bpython/translations/__init__.py | 7 +- bpython/urwid.py | 2 + setup.cfg | 12 ++ stubs/blessings.pyi | 47 ++++++ stubs/greenlet.pyi | 9 ++ stubs/msvcrt.pyi | 7 + stubs/pyperclip.pyi | 3 + stubs/rlcompleter.pyi | 3 + stubs/watchdog/__init__.pyi | 0 stubs/watchdog/events.pyi | 1 + stubs/watchdog/observers.pyi | 4 + stubs/xdg.pyi | 4 + 31 files changed, 427 insertions(+), 167 deletions(-) create mode 100644 stubs/blessings.pyi create mode 100644 stubs/greenlet.pyi create mode 100644 stubs/msvcrt.pyi create mode 100644 stubs/pyperclip.pyi create mode 100644 stubs/rlcompleter.pyi create mode 100644 stubs/watchdog/__init__.pyi create mode 100644 stubs/watchdog/events.pyi create mode 100644 stubs/watchdog/observers.pyi create mode 100644 stubs/xdg.pyi diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index adddd5d5a..839681b92 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -26,3 +26,20 @@ jobs: with: skip: '*.po' ignore_words_list: ba,te,deltion + + mypy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install mypy + pip install -r requirements.txt + pip install urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" numpy + pip install types-backports types-requests types-setuptools types-toml types-pygments + - name: Check with mypy + # for now only run on a few files to avoid slipping backward + run: mypy diff --git a/bpython/__init__.py b/bpython/__init__.py index f9048afa8..adc00c06b 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -23,7 +23,7 @@ import os.path try: - from ._version import __version__ as version + from ._version import __version__ as version # type: ignore except ImportError: version = "unknown" diff --git a/bpython/args.py b/bpython/args.py index 79ddcc679..1ab61d260 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -21,12 +21,17 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +# To gradually migrate to mypy we aren't setting these globally yet +# mypy: disallow_untyped_defs=True +# mypy: disallow_untyped_calls=True + """ Module to handle command line argument parsing, for all front-ends. """ import argparse -from typing import Tuple +from typing import Tuple, List, Optional, NoReturn, Callable +import code import curtsies import cwcwidth import greenlet @@ -50,11 +55,11 @@ class ArgumentParserFailed(ValueError): class RaisingArgumentParser(argparse.ArgumentParser): - def error(self, msg): + def error(self, msg: str) -> NoReturn: raise ArgumentParserFailed() -def version_banner(base="bpython") -> str: +def version_banner(base: str = "bpython") -> str: return _("{} version {} on top of Python {} {}").format( base, __version__, @@ -67,7 +72,14 @@ def copyright_banner() -> str: return _("{} See AUTHORS.rst for details.").format(__copyright__) -def parse(args, extras=None, ignore_stdin=False) -> Tuple: +Options = Tuple[str, str, Callable[[argparse._ArgumentGroup], None]] + + +def parse( + args: Optional[List[str]], + extras: Options = None, + ignore_stdin: bool = False, +) -> Tuple: """Receive an argument list - if None, use sys.argv - parse all args and take appropriate action. Also receive optional extra argument: this should be a tuple of (title, description, callback) @@ -197,7 +209,7 @@ def callback(group): logger.info(f"curtsies: {curtsies.__version__}") logger.info(f"cwcwidth: {cwcwidth.__version__}") logger.info(f"greenlet: {greenlet.__version__}") - logger.info(f"pygments: {pygments.__version__}") + logger.info(f"pygments: {pygments.__version__}") # type: ignore logger.info(f"requests: {requests.__version__}") logger.info( "environment:\n{}".format( @@ -214,7 +226,9 @@ def callback(group): return Config(options.config), options, options.args -def exec_code(interpreter, args): +def exec_code( + interpreter: code.InteractiveInterpreter, args: List[str] +) -> None: """ Helper to execute code in a given interpreter, e.g. to implement the behavior of python3 [-i] file.py @@ -230,9 +244,10 @@ def exec_code(interpreter, args): old_argv, sys.argv = sys.argv, args sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) spec = importlib.util.spec_from_loader("__console__", loader=None) + assert spec mod = importlib.util.module_from_spec(spec) sys.modules["__console__"] = mod - interpreter.locals.update(mod.__dict__) - interpreter.locals["__file__"] = args[0] + interpreter.locals.update(mod.__dict__) # type: ignore # TODO use a more specific type that has a .locals attribute + interpreter.locals["__file__"] = args[0] # type: ignore # TODO use a more specific type that has a .locals attribute interpreter.runsource(source, args[0], "exec") sys.argv = old_argv diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 3ec2a4578..fd91ea4af 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -21,6 +21,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +# To gradually migrate to mypy we aren't setting these globally yet +# mypy: disallow_untyped_defs=True +# mypy: disallow_untyped_calls=True import __main__ import abc @@ -33,12 +36,26 @@ import builtins from enum import Enum -from typing import Any, Dict, Iterator, List, Match, NoReturn, Set, Union, Tuple +from typing import ( + Any, + cast, + Dict, + Iterator, + List, + Match, + Optional, + Set, + Union, + Tuple, + Type, + Sequence, +) from . import inspection from . import line as lineparts from .line import LinePart from .lazyre import LazyReCompile from .simpleeval import safe_eval, evaluate_current_expression, EvaluationError +from .importcompletion import ModuleGatherer # Autocomplete modes @@ -49,7 +66,7 @@ class AutocompleteModes(Enum): FUZZY = "fuzzy" @classmethod - def from_string(cls, value) -> Union[Any, None]: + def from_string(cls, value: str) -> Optional[Any]: if value.upper() in cls.__members__: return cls.__members__[value.upper()] return None @@ -166,7 +183,7 @@ def after_last_dot(name: str) -> str: return name.rstrip(".").rsplit(".")[-1] -def few_enough_underscores(current, match) -> bool: +def few_enough_underscores(current: str, match: str) -> bool: """Returns whether match should be shown based on current if current is _, True if match starts with 0 or 1 underscore @@ -180,19 +197,19 @@ def few_enough_underscores(current, match) -> bool: return not match.startswith("_") -def method_match_none(word, size, text) -> bool: +def method_match_none(word: str, size: int, text: str) -> bool: return False -def method_match_simple(word, size, text) -> bool: +def method_match_simple(word: str, size: int, text: str) -> bool: return word[:size] == text -def method_match_substring(word, size, text) -> bool: +def method_match_substring(word: str, size: int, text: str) -> bool: return text in word -def method_match_fuzzy(word, size, text) -> Union[Match, None]: +def method_match_fuzzy(word: str, size: int, text: str) -> Optional[Match]: s = r".*%s.*" % ".*".join(list(text)) return re.search(s, word) @@ -209,15 +226,17 @@ class BaseCompletionType: """Describes different completion types""" def __init__( - self, shown_before_tab: bool = True, mode=AutocompleteModes.SIMPLE + self, + shown_before_tab: bool = True, + mode: AutocompleteModes = AutocompleteModes.SIMPLE, ) -> None: self._shown_before_tab = shown_before_tab self.method_match = MODES_MAP[mode] @abc.abstractmethod def matches( - self, cursor_offset: int, line: str, **kwargs - ) -> Union[Set[str], None]: + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set[str]]: """Returns a list of possible matches given a line and cursor, or None if this completion type isn't applicable. @@ -236,7 +255,7 @@ def matches( raise NotImplementedError @abc.abstractmethod - def locate(self, cursor_offset: int, line: str) -> Union[LinePart, None]: + def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: """Returns a Linepart namedtuple instance or None given cursor and line A Linepart namedtuple contains a start, stop, and word. None is @@ -244,10 +263,12 @@ def locate(self, cursor_offset: int, line: str) -> Union[LinePart, None]: the cursor.""" raise NotImplementedError - def format(self, word): + def format(self, word: str) -> str: return word - def substitute(self, cursor_offset, line, match) -> Tuple[int, str]: + def substitute( + self, cursor_offset: int, line: str, match: str + ) -> Tuple[int, str]: """Returns a cursor offset and line with match swapped in""" lpart = self.locate(cursor_offset, line) assert lpart @@ -265,28 +286,32 @@ def shown_before_tab(self) -> bool: class CumulativeCompleter(BaseCompletionType): """Returns combined matches from several completers""" - def __init__(self, completers, mode=AutocompleteModes.SIMPLE) -> None: + def __init__( + self, + completers: Sequence[BaseCompletionType], + mode: AutocompleteModes = AutocompleteModes.SIMPLE, + ) -> None: if not completers: raise ValueError( "CumulativeCompleter requires at least one completer" ) - self._completers = completers + self._completers: Sequence[BaseCompletionType] = completers super().__init__(True, mode) - def locate(self, current_offset: int, line: str) -> Union[None, NoReturn]: + def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: for completer in self._completers: - return_value = completer.locate(current_offset, line) + return_value = completer.locate(cursor_offset, line) if return_value is not None: return return_value return None - def format(self, word): + def format(self, word: str) -> str: return self._completers[0].format(word) def matches( - self, cursor_offset: int, line: str, **kwargs - ) -> Union[None, Set]: + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: return_value = None all_matches = set() for completer in self._completers: @@ -301,28 +326,36 @@ def matches( class ImportCompletion(BaseCompletionType): - def __init__(self, module_gatherer, mode=AutocompleteModes.SIMPLE): + def __init__( + self, + module_gatherer: ModuleGatherer, + mode: AutocompleteModes = AutocompleteModes.SIMPLE, + ): super().__init__(False, mode) self.module_gatherer = module_gatherer - def matches(self, cursor_offset, line, **kwargs): + def matches( + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: return self.module_gatherer.complete(cursor_offset, line) - def locate(self, current_offset, line): - return lineparts.current_word(current_offset, line) + def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + return lineparts.current_word(cursor_offset, line) - def format(self, word): + def format(self, word: str) -> str: return after_last_dot(word) class FilenameCompletion(BaseCompletionType): - def __init__(self, mode=AutocompleteModes.SIMPLE): + def __init__(self, mode: AutocompleteModes = AutocompleteModes.SIMPLE): super().__init__(False, mode) - def safe_glob(self, pathname) -> Iterator: + def safe_glob(self, pathname: str) -> Iterator[str]: return glob.iglob(glob.escape(pathname) + "*") - def matches(self, cursor_offset, line, **kwargs) -> Union[None, set]: + def matches( + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: cs = lineparts.current_string(cursor_offset, line) if cs is None: return None @@ -337,10 +370,10 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[None, set]: matches.add(filename) return matches - def locate(self, current_offset, line): - return lineparts.current_string(current_offset, line) + def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + return lineparts.current_string(cursor_offset, line) - def format(self, filename): + def format(self, filename: str) -> str: filename.rstrip(os.sep).rsplit(os.sep)[-1] if os.sep in filename[:-1]: return filename[filename.rindex(os.sep, 0, -1) + 1 :] @@ -352,10 +385,12 @@ class AttrCompletion(BaseCompletionType): attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") - def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: + def matches( + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: if "locals_" not in kwargs: return None - locals_ = kwargs["locals_"] + locals_ = cast(Dict[str, Any], kwargs["locals_"]) r = self.locate(cursor_offset, line) if r is None: @@ -382,13 +417,13 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: if few_enough_underscores(r.word.split(".")[-1], m.split(".")[-1]) } - def locate(self, current_offset, line): - return lineparts.current_dotted_attribute(current_offset, line) + def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + return lineparts.current_dotted_attribute(cursor_offset, line) - def format(self, word): + def format(self, word: str) -> str: return after_last_dot(word) - def attr_matches(self, text, namespace) -> List: + def attr_matches(self, text: str, namespace: Dict[str, Any]) -> List: """Taken from rlcompleter.py and bent to my will.""" m = self.attr_matches_re.match(text) @@ -407,7 +442,7 @@ def attr_matches(self, text, namespace) -> List: matches = self.attr_lookup(obj, expr, attr) return matches - def attr_lookup(self, obj, expr, attr) -> List: + def attr_lookup(self, obj: Any, expr: str, attr: str) -> List: """Second half of attr_matches.""" words = self.list_attributes(obj) if inspection.hasattr_safe(obj, "__class__"): @@ -427,7 +462,7 @@ def attr_lookup(self, obj, expr, attr) -> List: matches.append(f"{expr}.{word}") return matches - def list_attributes(self, obj) -> List[str]: + def list_attributes(self, obj: Any) -> List[str]: # TODO: re-implement dir using getattr_static to avoid using # AttrCleaner here? with inspection.AttrCleaner(obj): @@ -435,7 +470,9 @@ def list_attributes(self, obj) -> List[str]: class DictKeyCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: + def matches( + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: if "locals_" not in kwargs: return None locals_ = kwargs["locals_"] @@ -458,15 +495,17 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: else: return None - def locate(self, current_offset, line) -> Union[LinePart, None]: - return lineparts.current_dict_key(current_offset, line) + def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + return lineparts.current_dict_key(cursor_offset, line) - def format(self, match): + def format(self, match: str) -> str: return match[:-1] class MagicMethodCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: + def matches( + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: if "current_block" not in kwargs: return None current_block = kwargs["current_block"] @@ -478,12 +517,14 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: return None return {name for name in MAGIC_METHODS if name.startswith(r.word)} - def locate(self, current_offset, line) -> Union[LinePart, None]: - return lineparts.current_method_definition_name(current_offset, line) + def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + return lineparts.current_method_definition_name(cursor_offset, line) class GlobalCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs) -> Union[Set, None]: + def matches( + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace that match. @@ -513,12 +554,14 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[Set, None]: matches.add(_callable_postfix(val, word)) return matches if matches else None - def locate(self, current_offset, line) -> Union[LinePart, None]: - return lineparts.current_single_word(current_offset, line) + def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + return lineparts.current_single_word(cursor_offset, line) class ParameterNameCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: + def matches( + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: if "argspec" not in kwargs: return None argspec = kwargs["argspec"] @@ -539,16 +582,18 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: ) return matches if matches else None - def locate(self, current_offset, line) -> Union[LinePart, None]: - return lineparts.current_word(current_offset, line) + def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + return lineparts.current_word(cursor_offset, line) class ExpressionAttributeCompletion(AttrCompletion): # could replace attr completion as a more general case with some work - def locate(self, current_offset, line) -> Union[LinePart, None]: - return lineparts.current_expression_attribute(current_offset, line) + def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + return lineparts.current_expression_attribute(cursor_offset, line) - def matches(self, cursor_offset, line, **kwargs) -> Union[Set, None]: + def matches( + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: if "locals_" not in kwargs: return None locals_ = kwargs["locals_"] @@ -573,17 +618,24 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[Set, None]: import jedi except ImportError: - class MultilineJediCompletion(BaseCompletionType): - def matches(self, cursor_offset, line, **kwargs) -> None: + class MultilineJediCompletion(BaseCompletionType): # type: ignore [no-redef] + def matches( + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: + return None + + def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: return None else: class JediCompletion(BaseCompletionType): - _orig_start: Union[int, None] + _orig_start: Optional[int] - def matches(self, cursor_offset, line, **kwargs) -> Union[None, Set]: + def matches( + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: if "history" not in kwargs: return None history = kwargs["history"] @@ -630,8 +682,10 @@ def locate(self, cursor_offset: int, line: str) -> LinePart: end = cursor_offset return LinePart(start, end, line[start:end]) - class MultilineJediCompletion(JediCompletion): - def matches(self, cursor_offset, line, **kwargs) -> Union[Set, None]: + class MultilineJediCompletion(JediCompletion): # type: ignore [no-redef] + def matches( + self, cursor_offset: int, line: str, **kwargs: Any + ) -> Optional[Set]: if "current_block" not in kwargs or "history" not in kwargs: return None current_block = kwargs["current_block"] @@ -648,7 +702,12 @@ def matches(self, cursor_offset, line, **kwargs) -> Union[Set, None]: return None -def get_completer(completers, cursor_offset, line, **kwargs): +def get_completer( + completers: Sequence[BaseCompletionType], + cursor_offset: int, + line: str, + **kwargs: Any, +) -> Tuple[List[str], Optional[BaseCompletionType]]: """Returns a list of matches and an applicable completer If no matches available, returns a tuple of an empty list and None @@ -683,7 +742,9 @@ def get_completer(completers, cursor_offset, line, **kwargs): return [], None -def get_default_completer(mode=AutocompleteModes.SIMPLE, module_gatherer=None): +def get_default_completer( + mode: AutocompleteModes, module_gatherer: ModuleGatherer +) -> Tuple[BaseCompletionType, ...]: return ( ( DictKeyCompletion(mode=mode), @@ -706,7 +767,7 @@ def get_default_completer(mode=AutocompleteModes.SIMPLE, module_gatherer=None): ) -def _callable_postfix(value, word): +def _callable_postfix(value: Any, word: str) -> str: """rlcompleter's _callable_postfix done right.""" if callable(value): word += "(" diff --git a/bpython/cli.py b/bpython/cli.py index baa2059da..28cc67c71 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -50,7 +50,7 @@ import struct import sys import time -from typing import Iterator, NoReturn +from typing import Iterator, NoReturn, List import unicodedata from dataclasses import dataclass @@ -144,7 +144,7 @@ def writelines(self, l) -> None: for s in l: self.write(s) - def isatty(self) -> True: + def isatty(self) -> bool: # some third party (amongst them mercurial) depend on this return True @@ -161,7 +161,7 @@ def __init__(self, interface) -> None: self.encoding = getpreferredencoding() self.interface = interface - self.buffer = list() + self.buffer: List[str] = list() def __iter__(self) -> Iterator: return iter(self.readlines()) @@ -175,7 +175,7 @@ def write(self, value) -> NoReturn: # others, so here's a hack to keep them happy raise OSError(errno.EBADF, "sys.stdin is read-only") - def isatty(self) -> True: + def isatty(self) -> bool: return True def readline(self, size=-1): diff --git a/bpython/config.py b/bpython/config.py index 48686610e..0127b0cbc 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -21,6 +21,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +# To gradually migrate to mypy we aren't setting these globally yet +# mypy: disallow_untyped_defs=True +# mypy: disallow_untyped_calls=True + import os import sys import locale diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 10fab77f4..86d33cf3f 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -1,3 +1,9 @@ +# To gradually migrate to mypy we aren't setting these globally yet +# mypy: disallow_untyped_defs=True +# mypy: disallow_untyped_calls=True + +import argparse +import code import collections import logging import sys @@ -16,11 +22,43 @@ from .repl import extract_exit_value from .translations import _ +from typing import ( + Any, + Dict, + List, + Callable, + Union, + Sequence, + Tuple, + Optional, + Generator, +) +from typing_extensions import Literal, Protocol + logger = logging.getLogger(__name__) +class SupportsEventGeneration(Protocol): + def send( + self, timeout: Optional[float] + ) -> Union[str, curtsies.events.Event, None]: + ... + + def __iter__(self) -> "SupportsEventGeneration": + ... + + def __next__(self) -> Union[str, curtsies.events.Event, None]: + ... + + class FullCurtsiesRepl(BaseRepl): - def __init__(self, config, locals_, banner, interp=None) -> None: + def __init__( + self, + config: Config, + locals_: Optional[Dict[str, Any]], + banner: Optional[str], + interp: code.InteractiveInterpreter = None, + ) -> None: self.input_generator = curtsies.input.Input( keynames="curtsies", sigint_event=True, paste_threshold=None ) @@ -32,13 +70,13 @@ def __init__(self, config, locals_, banner, interp=None) -> None: extra_bytes_callback=self.input_generator.unget_bytes, ) - self._request_refresh_callback = self.input_generator.event_trigger( - events.RefreshRequestEvent - ) - self._schedule_refresh_callback = ( - self.input_generator.scheduled_event_trigger( - events.ScheduledRefreshRequestEvent - ) + self._request_refresh_callback: Callable[ + [], None + ] = self.input_generator.event_trigger(events.RefreshRequestEvent) + self._schedule_refresh_callback: Callable[ + [float], None + ] = self.input_generator.scheduled_event_trigger( + events.ScheduledRefreshRequestEvent ) self._request_reload_callback = ( self.input_generator.threadsafe_event_trigger(events.ReloadEvent) @@ -61,40 +99,42 @@ def __init__(self, config, locals_, banner, interp=None) -> None: orig_tcattrs=self.input_generator.original_stty, ) - def _request_refresh(self): + def _request_refresh(self) -> None: return self._request_refresh_callback() - def _schedule_refresh(self, when="now"): + def _schedule_refresh(self, when: float) -> None: return self._schedule_refresh_callback(when) - def _request_reload(self, files_modified=("?",)): + def _request_reload(self, files_modified: Sequence[str] = ("?",)) -> None: return self._request_reload_callback(files_modified) - def interrupting_refresh(self): + def interrupting_refresh(self) -> None: return self._interrupting_refresh_callback() - def request_undo(self, n=1): + def request_undo(self, n: int = 1) -> None: return self._request_undo_callback(n=n) - def get_term_hw(self): + def get_term_hw(self) -> Tuple[int, int]: return self.window.get_term_hw() - def get_cursor_vertical_diff(self): + def get_cursor_vertical_diff(self) -> int: return self.window.get_cursor_vertical_diff() - def get_top_usable_line(self): + def get_top_usable_line(self) -> int: return self.window.top_usable_row - def on_suspend(self): + def on_suspend(self) -> None: self.window.__exit__(None, None, None) self.input_generator.__exit__(None, None, None) - def after_suspend(self): + def after_suspend(self) -> None: self.input_generator.__enter__() self.window.__enter__() self.interrupting_refresh() - def process_event_and_paint(self, e): + def process_event_and_paint( + self, e: Union[str, curtsies.events.Event, None] + ) -> None: """If None is passed in, just paint the screen""" try: if e is not None: @@ -112,7 +152,11 @@ def process_event_and_paint(self, e): scrolled = self.window.render_to_terminal(array, cursor_pos) self.scroll_offset += scrolled - def mainloop(self, interactive=True, paste=None): + def mainloop( + self, + interactive: bool = True, + paste: Optional[curtsies.events.PasteEvent] = None, + ) -> None: if interactive: # Add custom help command # TODO: add methods to run the code @@ -137,14 +181,19 @@ def mainloop(self, interactive=True, paste=None): self.process_event_and_paint(e) -def main(args=None, locals_=None, banner=None, welcome_message=None): +def main( + args: List[str] = None, + locals_: Dict[str, Any] = None, + banner: str = None, + welcome_message: str = None, +) -> Any: """ banner is displayed directly after the version information. welcome_message is passed on to Repl and displayed in the statusbar. """ translations.init() - def curtsies_arguments(parser): + def curtsies_arguments(parser: argparse._ArgumentGroup) -> None: parser.add_argument( "--paste", "-p", @@ -163,10 +212,10 @@ def curtsies_arguments(parser): interp = None paste = None + exit_value: Tuple[Any, ...] = () if exec_args: if not options: raise ValueError("don't pass in exec_args without options") - exit_value = () if options.paste: paste = curtsies.events.PasteEvent() encoding = inspection.get_encoding_file(exec_args[0]) @@ -196,16 +245,18 @@ def curtsies_arguments(parser): with repl.window as win: with repl: repl.height, repl.width = win.t.height, win.t.width - exit_value = repl.mainloop(True, paste) + repl.mainloop(True, paste) except (SystemExitFromCodeRunner, SystemExit) as e: exit_value = e.args return extract_exit_value(exit_value) -def _combined_events(event_provider, paste_threshold): +def _combined_events( + event_provider: "SupportsEventGeneration", paste_threshold: int +) -> Generator[Union[str, curtsies.events.Event, None], Optional[float], None]: """Combines consecutive keypress events into paste events.""" timeout = yield "nonsense_event" # so send can be used immediately - queue = collections.deque() + queue: collections.deque = collections.deque() while True: e = event_provider.send(timeout) if isinstance(e, curtsies.events.Event): @@ -230,7 +281,9 @@ def _combined_events(event_provider, paste_threshold): timeout = yield queue.popleft() -def combined_events(event_provider, paste_threshold=3): +def combined_events( + event_provider: SupportsEventGeneration, paste_threshold: int = 3 +) -> SupportsEventGeneration: g = _combined_events(event_provider, paste_threshold) next(g) return g diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 314767a53..e3607180c 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -14,7 +14,7 @@ def ModuleChangedEventHandler(*args): else: - class ModuleChangedEventHandler(FileSystemEventHandler): + class ModuleChangedEventHandler(FileSystemEventHandler): # type: ignore [no-redef] def __init__(self, paths, on_change): self.dirs = defaultdict(set) self.on_change = on_change diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 51fd28d35..79622d149 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -78,7 +78,7 @@ def _check_for_expired_message(self): ): self._message = "" - def process_event(self, e): + def process_event(self, e) -> None: """Returns True if shutting down""" assert self.in_prompt or self.in_confirm or self.waiting_for_refresh if isinstance(e, RefreshRequestEvent): diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index a48bc429a..7f7c2fcc7 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,4 +1,5 @@ import sys +from typing import Any, Dict from pygments.token import Generic, Token, Keyword, Name, Comment, String from pygments.token import Error, Literal, Number, Operator, Punctuation @@ -59,7 +60,7 @@ def format(self, tokensource, outfile): class Interp(ReplInterpreter): - def __init__(self, locals=None, encoding=None): + def __init__(self, locals: Dict[str, Any] = None, encoding=None): """Constructor. We include an argument for the outfile to pass to the formatter for it @@ -75,7 +76,7 @@ def write(err_line): Accepts FmtStrs so interpreters can output them""" sys.stderr.write(str(err_line)) - self.write = write + self.write = write # type: ignore self.outfile = self def writetb(self, lines): diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 58aa6127d..85965f4f6 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1,3 +1,4 @@ +import code import contextlib import errno import itertools @@ -12,6 +13,8 @@ import unicodedata from enum import Enum +from typing import Dict, Any, List, Optional, Tuple, Union, cast + import blessings import cwcwidth import greenlet @@ -32,6 +35,7 @@ from pygments.lexers import Python3Lexer from . import events as bpythonevents, sitefix, replpainter as paint +from ..config import Config from .coderunner import ( CodeRunner, FakeOutput, @@ -98,7 +102,7 @@ def __init__(self, coderunner, repl, configured_edit_keys=None): else: self.rl_char_sequences = edit_keys - def process_event(self, e): + def process_event(self, e: Union[events.Event, str]) -> None: assert self.has_focus logger.debug("fake input processing event %r", e) @@ -279,7 +283,7 @@ def _process_ps(ps, default_ps: str): if not isinstance(ps, str): return ps - return ps if cwcwidth.wcswidth(ps) >= 0 else default_ps + return ps if cwcwidth.wcswidth(ps, None) >= 0 else default_ps class BaseRepl(Repl): @@ -306,11 +310,11 @@ class BaseRepl(Repl): def __init__( self, - config, - locals_=None, - banner=None, - interp=None, - orig_tcattrs=None, + config: Config, + locals_: Dict[str, Any] = None, + banner: str = None, + interp: code.InteractiveInterpreter = None, + orig_tcattrs: List[Any] = None, ): """ locals_ is a mapping of locals to pass into the interpreter @@ -328,7 +332,7 @@ def __init__( if interp is None: interp = Interp(locals=locals_) - interp.write = self.send_to_stdouterr + interp.write = self.send_to_stdouterr # type: ignore if banner is None: if config.help_key: banner = ( @@ -370,7 +374,7 @@ def __init__( # this is every line that's been displayed (input and output) # as with formatting applied. Logical lines that exceeded the terminal width # at the time of output are split across multiple entries in this list. - self.display_lines = [] + self.display_lines: List[FmtStr] = [] # this is every line that's been executed; it gets smaller on rewind self.history = [] @@ -380,12 +384,12 @@ def __init__( # Entries are tuples, where # - the first element the line (string, not fmtsr) # - the second element is one of 2 global constants: "input" or "output" - # (use LineTypeTranslator.INPUT or LineTypeTranslator.OUTPUT to avoid typing these strings) - self.all_logical_lines = [] + # (use LineType.INPUT or LineType.OUTPUT to avoid typing these strings) + self.all_logical_lines: List[Tuple[str, LineType]] = [] # formatted version of lines in the buffer kept around so we can # unhighlight parens using self.reprint_line as called by bpython.Repl - self.display_buffer = [] + self.display_buffer: List[FmtStr] = [] # how many times display has been scrolled down # because there wasn't room to display everything @@ -394,7 +398,7 @@ def __init__( # cursor position relative to start of current_line, 0 is first char self._cursor_offset = 0 - self.orig_tcattrs = orig_tcattrs + self.orig_tcattrs: Optional[List[Any]] = orig_tcattrs self.coderunner = CodeRunner(self.interp, self.request_refresh) @@ -426,7 +430,7 @@ def __init__( # some commands act differently based on the prev event # this list doesn't include instances of event.Event, # only keypress-type events (no refresh screen events etc.) - self.last_events = [None] * 50 + self.last_events: List[Optional[str]] = [None] * 50 # displays prev events in a column on the right hand side self.presentation_mode = False @@ -444,8 +448,10 @@ def __init__( self.original_modules = set(sys.modules.keys()) - self.width = None - self.height = None + # as long as the first event received is a window resize event, + # this works fine... + self.width: int = cast(int, None) + self.height: int = cast(int, None) self.status_bar.message(banner) @@ -468,7 +474,7 @@ def get_term_hw(self): """Returns the current width and height of the display area.""" return (50, 10) - def _schedule_refresh(self, when="now"): + def _schedule_refresh(self, when: float): """Arrange for the bpython display to be refreshed soon. This method will be called when the Repl wants the display to be @@ -611,7 +617,7 @@ def clean_up_current_line_for_exit(self): self.unhighlight_paren() # Event handling - def process_event(self, e): + def process_event(self, e: Union[events.Event, str]) -> Optional[bool]: """Returns True if shutting down, otherwise returns None. Mostly mutates state of Repl object""" @@ -621,9 +627,10 @@ def process_event(self, e): else: self.last_events.append(e) self.last_events.pop(0) - return self.process_key_event(e) + self.process_key_event(e) + return None - def process_control_event(self, e): + def process_control_event(self, e) -> Optional[bool]: if isinstance(e, bpythonevents.ScheduledRefreshRequestEvent): # This is a scheduled refresh - it's really just a refresh (so nop) @@ -638,7 +645,7 @@ def process_control_event(self, e): self.run_code_and_maybe_finish() elif self.status_bar.has_focus: - return self.status_bar.process_event(e) + self.status_bar.process_event(e) # handles paste events for both stdin and repl elif isinstance(e, events.PasteEvent): @@ -678,12 +685,11 @@ def process_control_event(self, e): self.undo(n=e.n) elif self.stdin.has_focus: - return self.stdin.process_event(e) + self.stdin.process_event(e) elif isinstance(e, events.SigIntEvent): logger.debug("received sigint event") self.keyboard_interrupt() - return elif isinstance(e, bpythonevents.ReloadEvent): if self.watching_files: @@ -695,8 +701,9 @@ def process_control_event(self, e): else: raise ValueError("Don't know how to handle event type: %r" % e) + return None - def process_key_event(self, e): + def process_key_event(self, e: str) -> None: # To find the curtsies name for a keypress, try # python -m curtsies.events if self.status_bar.has_focus: @@ -751,7 +758,7 @@ def process_key_event(self, e): elif e in key_dispatch[self.config.reimport_key]: self.clear_modules_and_reevaluate() elif e in key_dispatch[self.config.toggle_file_watch_key]: - return self.toggle_file_watch() + self.toggle_file_watch() elif e in key_dispatch[self.config.clear_screen_key]: self.request_paint_to_clear_screen = True elif e in key_dispatch[self.config.show_source_key]: @@ -1427,7 +1434,7 @@ def paint( user_quit=False, try_preserve_history_height=30, min_infobox_height=5, - ): + ) -> Tuple[FSArray, Tuple[int, int]]: """Returns an array of min_height or more rows and width columns, plus cursor position @@ -1585,11 +1592,12 @@ def move_screen_up(current_line_start_row): type(self.current_stdouterr_line), self.current_stdouterr_line, ) - stdouterr_width = ( - self.current_stdouterr_line.width - if isinstance(self.current_stdouterr_line, FmtStr) - else wcswidth(self.current_stdouterr_line) - ) + # mypy can't do ternary type guards yet + stdouterr = self.current_stdouterr_line + if isinstance(stdouterr, FmtStr): + stdouterr_width = stdouterr.width + else: + stdouterr_width = len(stdouterr) cursor_row, cursor_column = divmod( stdouterr_width + wcswidth( @@ -1618,8 +1626,8 @@ def move_screen_up(current_line_start_row): ) else: # Common case for determining cursor position cursor_row, cursor_column = divmod( - wcswidth(self.current_cursor_line_without_suggestion.s) - - wcswidth(self.current_line) + wcswidth(self.current_cursor_line_without_suggestion.s, None) + - wcswidth(self.current_line, None) + wcswidth(self.current_line, max(0, self.cursor_offset)) + self.number_of_padding_chars_on_current_cursor_line(), width, @@ -1815,7 +1823,7 @@ def echo(self, msg, redraw=True): @property def cpos(self): - "many WATs were had - it's the pos from the end of the line back" "" + "many WATs were had - it's the pos from the end of the line back" return len(self.current_line) - self.cursor_offset def reprint_line(self, lineno, tokens): @@ -1930,7 +1938,7 @@ def reevaluate(self, new_code=False): self._cursor_offset = 0 self.current_line = "" - def initialize_interp(self): + def initialize_interp(self) -> None: self.coderunner.interp.locals["_repl"] = self self.coderunner.interp.runsource( "from bpython.curtsiesfrontend._internal import _Helper\n" diff --git a/bpython/inspection.py b/bpython/inspection.py index 4c0dbada4..7f95fbd20 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -336,7 +336,7 @@ def get_encoding(obj): return "utf8" -def get_encoding_file(fname): +def get_encoding_file(fname: str) -> str: """Try to obtain encoding information from a Python source file.""" with open(fname, encoding="ascii", errors="ignore") as f: for unused in range(2): @@ -347,7 +347,7 @@ def get_encoding_file(fname): return "utf8" -def getattr_safe(obj, name): +def getattr_safe(obj: Any, name: str): """side effect free getattr (calls getattr_static).""" result = inspect.getattr_static(obj, name) # Slots are a MemberDescriptorType @@ -356,7 +356,7 @@ def getattr_safe(obj, name): return result -def hasattr_safe(obj, name): +def hasattr_safe(obj: Any, name: str) -> bool: try: getattr_safe(obj, name) return True diff --git a/bpython/lazyre.py b/bpython/lazyre.py index fbbdd38d8..8f1e70995 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -21,12 +21,12 @@ # THE SOFTWARE. import re -from typing import Optional, Iterator +from typing import Optional, Iterator, Pattern, Match, Optional try: from functools import cached_property except ImportError: - from backports.cached_property import cached_property # type: ignore + from backports.cached_property import cached_property # type: ignore [no-redef] class LazyReCompile: @@ -40,16 +40,16 @@ def __init__(self, regex: str, flags: int = 0) -> None: self.flags = flags @cached_property - def compiled(self): + def compiled(self) -> Pattern[str]: return re.compile(self.regex, self.flags) def finditer(self, *args, **kwargs): return self.compiled.finditer(*args, **kwargs) - def search(self, *args, **kwargs): + def search(self, *args, **kwargs) -> Optional[Match[str]]: return self.compiled.search(*args, **kwargs) - def match(self, *args, **kwargs): + def match(self, *args, **kwargs) -> Optional[Match[str]]: return self.compiled.match(*args, **kwargs) def sub(self, *args, **kwargs) -> str: diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index daf7251d9..e1d94a157 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -64,7 +64,7 @@ def _bpython_clear_linecache(): # Monkey-patch the linecache module so that we're able # to hold our command history there and have it persist -linecache.cache = BPythonLinecache(linecache.cache) +linecache.cache = BPythonLinecache(linecache.cache) # type: ignore linecache.clearcache = _bpython_clear_linecache diff --git a/bpython/repl.py b/bpython/repl.py index ba9acf4d8..e261e61ad 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -33,11 +33,13 @@ import textwrap import time import traceback +from abc import abstractmethod from itertools import takewhile from pathlib import Path from pygments.lexers import Python3Lexer from pygments.token import Token from types import ModuleType +from typing import cast, Tuple, Any have_pyperclip = True try: @@ -451,11 +453,11 @@ def __init__(self, interp, config): @property def ps1(self) -> str: - return getattr(sys, "ps1", ">>> ") + return cast(str, getattr(sys, "ps1", ">>> ")) @property def ps2(self) -> str: - return getattr(sys, "ps2", "... ") + return cast(str, getattr(sys, "ps2", "... ")) def startup(self): """ @@ -769,6 +771,10 @@ def line_is_empty(line): indentation = 0 return indentation + @abstractmethod + def getstdout(self) -> str: + raise NotImplementedError() + def get_session_formatted_for_file(self) -> str: """Format the stdout buffer to something suitable for writing to disk, i.e. without >>> and ... at input lines and with "# OUT: " prepended to @@ -1214,7 +1220,7 @@ def token_is_any_of(token): return token_is_any_of -def extract_exit_value(args): +def extract_exit_value(args: Tuple[Any, ...]) -> Any: """Given the arguments passed to `SystemExit`, return the value that should be passed to `sys.exit`. """ diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index ef6dd53b9..c5e14cf5d 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -28,6 +28,7 @@ import ast import sys import builtins +from typing import Dict, Any from . import line as line_properties from .inspection import getattr_safe @@ -44,7 +45,7 @@ class EvaluationError(Exception): """Raised if an exception occurred in safe_eval.""" -def safe_eval(expr, namespace): +def safe_eval(expr: str, namespace: Dict[str, Any]) -> Any: """Not all that safe, just catches some errors""" try: return eval(expr, namespace) @@ -214,7 +215,9 @@ def find_attribute_with_name(node, name): return r -def evaluate_current_expression(cursor_offset, line, namespace=None): +def evaluate_current_expression( + cursor_offset: int, line: str, namespace: Dict[str, Any] = None +): """ Return evaluated expression to the right of the dot of current attribute. diff --git a/bpython/test/test_crashers.py b/bpython/test/test_crashers.py index 64abff3e3..051e5b691 100644 --- a/bpython/test/test_crashers.py +++ b/bpython/test/test_crashers.py @@ -17,10 +17,10 @@ from twisted.trial.unittest import TestCase as TrialTestCase except ImportError: - class TrialTestCase: + class TrialTestCase: # type: ignore [no-redef] pass - reactor = None + reactor = None # type: ignore try: import urwid diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 2ed33097b..a6e4c7866 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -86,8 +86,7 @@ def test_last_word(self): self.assertEqual(curtsiesrepl._last_word("a"), "a") self.assertEqual(curtsiesrepl._last_word("a b"), "b") - # this is the behavior of bash - not currently implemented - @unittest.skip + @unittest.skip("this is the behavior of bash - not currently implemented") def test_get_last_word_with_prev_line(self): self.repl.rl_history.entries = ["1", "2 3", "4 5 6"] self.repl._set_current_line("abcde") @@ -300,7 +299,7 @@ def test_simple(self): self.assertEqual(self.repl.predicted_indent("def asdf():"), 4) self.assertEqual(self.repl.predicted_indent("def asdf(): return 7"), 0) - @unittest.skip + @unittest.skip("This would be interesting") def test_complex(self): self.assertEqual(self.repl.predicted_indent("[a, "), 1) self.assertEqual(self.repl.predicted_indent("reduce(asdfasdf, "), 7) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index fecb848b3..50be0c3a2 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -12,7 +12,7 @@ try: import numpy except ImportError: - numpy = None + numpy = None # type: ignore foo_ascii_only = '''def foo(): @@ -191,7 +191,7 @@ def __mro__(self): a = 1 -member_descriptor = type(Slots.s1) +member_descriptor = type(Slots.s1) # type: ignore class TestSafeGetAttribute(unittest.TestCase): diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index c2e23f806..13c498025 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -2,13 +2,14 @@ import locale import os.path import sys +from typing import cast, List from .. import package_dir -translator = None +translator: gettext.NullTranslations = cast(gettext.NullTranslations, None) -def _(message): +def _(message) -> str: return translator.gettext(message) @@ -16,7 +17,7 @@ def ngettext(singular, plural, n): return translator.ngettext(singular, plural, n) -def init(locale_dir=None, languages=None): +def init(locale_dir: str = None, languages: List[str] = None) -> None: try: locale.setlocale(locale.LC_ALL, "") except locale.Error: diff --git a/bpython/urwid.py b/bpython/urwid.py index 1f63b1370..e3aab75c2 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +# This whole file typing TODO +# type: ignore """bpython backend based on Urwid. diff --git a/setup.cfg b/setup.cfg index 89b6cde61..1efed779c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -65,3 +65,15 @@ msgid_bugs_address = https://github.com/bpython/bpython/issues builder = man source_dir = doc/sphinx/source build_dir = build + +[mypy] +warn_return_any = True +warn_unused_configs = True +mypy_path=stubs +files=bpython + +[mypy-jedi] +ignore_missing_imports = True + +[mypy-urwid] +ignore_missing_imports = True diff --git a/stubs/blessings.pyi b/stubs/blessings.pyi new file mode 100644 index 000000000..66fd96216 --- /dev/null +++ b/stubs/blessings.pyi @@ -0,0 +1,47 @@ +from typing import ContextManager, Text, IO + +class Terminal: + def __init__(self, stream=None, force_styling=False): + # type: (IO, bool) -> None + pass + def location(self, x=None, y=None): + # type: (int, int) -> ContextManager + pass + @property + def hide_cursor(self): + # type: () -> Text + pass + @property + def normal_cursor(self): + # type: () -> Text + pass + @property + def height(self): + # type: () -> int + pass + @property + def width(self): + # type: () -> int + pass + def fullscreen(self): + # type: () -> ContextManager + pass + def move(self, y, x): + # type: (int, int) -> Text + pass + @property + def clear_eol(self): + # type: () -> Text + pass + @property + def clear_bol(self): + # type: () -> Text + pass + @property + def clear_eos(self): + # type: () -> Text + pass + @property + def clear_eos(self): + # type: () -> Text + pass diff --git a/stubs/greenlet.pyi b/stubs/greenlet.pyi new file mode 100644 index 000000000..778c827ef --- /dev/null +++ b/stubs/greenlet.pyi @@ -0,0 +1,9 @@ +from typing import Any, Callable + +__version__: str + +def getcurrent() -> None: ... + +class greenlet: + def __init__(self, func: Callable[[], Any]): ... + def switch(self, value: Any = None) -> Any: ... diff --git a/stubs/msvcrt.pyi b/stubs/msvcrt.pyi new file mode 100644 index 000000000..2e99c9008 --- /dev/null +++ b/stubs/msvcrt.pyi @@ -0,0 +1,7 @@ +# The real types seem only available on the Windows platform, +# but it seems annoying to need to run typechecking once per platform +# https://github.com/python/typeshed/blob/master/stdlib/msvcrt.pyi +def locking(__fd: int, __mode: int, __nbytes: int) -> None: ... + +LK_NBLCK: int +LK_UNLCK: int diff --git a/stubs/pyperclip.pyi b/stubs/pyperclip.pyi new file mode 100644 index 000000000..3968c20a6 --- /dev/null +++ b/stubs/pyperclip.pyi @@ -0,0 +1,3 @@ +def copy(content: str): ... + +class PyperclipException(Exception): ... diff --git a/stubs/rlcompleter.pyi b/stubs/rlcompleter.pyi new file mode 100644 index 000000000..bbc871ada --- /dev/null +++ b/stubs/rlcompleter.pyi @@ -0,0 +1,3 @@ +from typing import Any + +def get_class_members(class_: Any): ... diff --git a/stubs/watchdog/__init__.pyi b/stubs/watchdog/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/watchdog/events.pyi b/stubs/watchdog/events.pyi new file mode 100644 index 000000000..6e17bd6df --- /dev/null +++ b/stubs/watchdog/events.pyi @@ -0,0 +1 @@ +class FileSystemEventHandler: ... diff --git a/stubs/watchdog/observers.pyi b/stubs/watchdog/observers.pyi new file mode 100644 index 000000000..7db3099fb --- /dev/null +++ b/stubs/watchdog/observers.pyi @@ -0,0 +1,4 @@ +class Observer: + def start(self): ... + def schedule(self, dirname: str, recursive: bool): ... + def unschedule_all(self): ... diff --git a/stubs/xdg.pyi b/stubs/xdg.pyi new file mode 100644 index 000000000..db7d63e03 --- /dev/null +++ b/stubs/xdg.pyi @@ -0,0 +1,4 @@ +from typing import ClassVar + +class BaseDirectory: + xdg_config_home: ClassVar[str] From 459753e97e724eeeba4c950c54b7a9d6977e4163 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 12 Oct 2021 21:31:39 +0200 Subject: [PATCH 1317/1650] Update changelog --- CHANGELOG.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 28cd66af2..5f956a1ec 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,12 +6,36 @@ Changelog General information: +* The #bpython channel has moved to OFTC. +* Type annotations have been added to the bpython code base. +* Declarative build configuration is used as much as possible. + New features: +* #883: Allow auto-completion to be disabled +* #841: Respect locals when using bpython.embed +* Use pyperclip for better clipboard handling + Fixes: +* #879: Iterate over all completers until a successful one is found +* #882: Handle errors in theme configuration without crashing +* #884: Fix writing of b"" on fake stdout +* #888: Read PYTHONSTARTUP with utf8 as encoding +* #896: Use default sys.ps1 and sys.ps2 if user specified ones are not usable +* #902: Do not crash when encountering unreadable files while processing modules for import completion +* #909: Fix sys.stdin.readlin +* #917: Fix tab completion for dict keys +* #919: Replicate python behavior when running with -i and a non-existing file + Changes to dependencies: +* pyperclip is a new optional dependency for clipboard support +* backports.cached-property is now required for Python < 3.8 +* dataclasses is now required for Python < 3.7 + +Support for Python 3.10 has been added. + 0.21 ---- From 03e2ead4edf09af8e2928732a2427caababb53ad Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 12 Oct 2021 21:56:29 +0200 Subject: [PATCH 1318/1650] Update German translation --- bpython/translations/de/LC_MESSAGES/bpython.po | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 95bd6cc84..718acbb7f 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -1,7 +1,7 @@ # German translations for bpython. -# Copyright (C) 2012-2013 bpython developers +# Copyright (C) 2012-2021 bpython developers # This file is distributed under the same license as the bpython project. -# Sebastian Ramacher , 2012-2013. +# Sebastian Ramacher , 2012-2021. # msgid "" msgstr "" @@ -64,7 +64,7 @@ msgstr "Datei für Ausgabe von Log-Nachrichten" #: bpython/args.py:146 msgid "File to execute and additional arguments passed on to the executed script." -msgstr "" +msgstr "Auszuführende Datei und zusätzliche Argumente, die an das Script übergeben werden sollen." #: bpython/cli.py:312 bpython/urwid.py:537 msgid "y" @@ -206,20 +206,20 @@ msgstr "Inhalt wurde in Zwischenablage kopiert." #: bpython/repl.py:884 msgid "Pastebin buffer? (y/N) " -msgstr "" +msgstr "Buffer bei Pastebin hochladen? (j/N)" #: bpython/repl.py:886 msgid "Pastebin aborted." -msgstr "" +msgstr "Hochladen zu Pastebin abgebrochen." #: bpython/repl.py:894 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" -msgstr "" +msgstr "Duplizierte Daten zu Pastebin hochgeladen. Vorherige URL: %s. URL zum Löschen: %s" #: bpython/repl.py:900 msgid "Posting data to pastebin..." -msgstr "Lade Daten hoch..." +msgstr "Lade Daten hoch zu Pastebin..." #: bpython/repl.py:904 #, python-format From f591116efa8ff4139bb72b4e9d10cc4c25c6b9a9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 12 Oct 2021 21:59:07 +0200 Subject: [PATCH 1319/1650] Update translations --- bpython/translations/bpython.pot | 141 +++++------ .../translations/de/LC_MESSAGES/bpython.po | 228 +++++++++--------- .../translations/es_ES/LC_MESSAGES/bpython.po | 139 +++++------ .../translations/fr_FR/LC_MESSAGES/bpython.po | 139 +++++------ .../translations/it_IT/LC_MESSAGES/bpython.po | 139 +++++------ .../translations/nl_NL/LC_MESSAGES/bpython.po | 139 +++++------ 6 files changed, 469 insertions(+), 456 deletions(-) diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index 24c9cdd8a..e11140ed2 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: bpython 0.22.dev28\n" +"Project-Id-Version: bpython 0.22.dev123\n" "Report-Msgid-Bugs-To: https://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-02-14 17:29+0100\n" +"POT-Creation-Date: 2021-10-12 21:58+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,15 +17,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:49 +#: bpython/args.py:63 msgid "{} version {} on top of Python {} {}" msgstr "" -#: bpython/args.py:58 +#: bpython/args.py:72 msgid "{} See AUTHORS.rst for details." msgstr "" -#: bpython/args.py:95 +#: bpython/args.py:116 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -33,82 +33,83 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:105 +#: bpython/args.py:127 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:111 +#: bpython/args.py:133 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:117 +#: bpython/args.py:139 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:123 +#: bpython/args.py:145 msgid "Print version and exit." msgstr "" -#: bpython/args.py:130 +#: bpython/args.py:152 msgid "Set log level for logging" msgstr "" -#: bpython/args.py:135 +#: bpython/args.py:157 msgid "Log output file" msgstr "" -#: bpython/args.py:146 +#: bpython/args.py:168 msgid "File to execute and additional arguments passed on to the executed script." msgstr "" -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 +#: bpython/urwid.py:539 msgid "y" msgstr "" -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/urwid.py:539 msgid "yes" msgstr "" -#: bpython/cli.py:1692 +#: bpython/cli.py:1696 msgid "Rewind" msgstr "" -#: bpython/cli.py:1693 +#: bpython/cli.py:1697 msgid "Save" msgstr "" -#: bpython/cli.py:1694 +#: bpython/cli.py:1698 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1695 +#: bpython/cli.py:1699 msgid "Pager" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1700 msgid "Show Source" msgstr "" -#: bpython/cli.py:1943 +#: bpython/cli.py:1947 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsies.py:136 +#: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:142 +#: bpython/curtsies.py:207 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:208 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" -#: bpython/history.py:224 +#: bpython/history.py:250 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" @@ -134,212 +135,212 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:653 +#: bpython/repl.py:644 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:649 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:663 +#: bpython/repl.py:654 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:665 +#: bpython/repl.py:656 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:820 +#: bpython/repl.py:801 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:803 bpython/repl.py:806 bpython/repl.py:830 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:817 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:844 +#: bpython/repl.py:825 msgid "overwrite" msgstr "" -#: bpython/repl.py:846 +#: bpython/repl.py:827 msgid "append" msgstr "" -#: bpython/repl.py:858 bpython/repl.py:1165 +#: bpython/repl.py:839 bpython/repl.py:1143 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:860 +#: bpython/repl.py:841 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:866 +#: bpython/repl.py:847 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:873 +#: bpython/repl.py:854 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:856 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:884 +#: bpython/repl.py:865 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:867 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:875 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:881 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:885 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:913 +#: bpython/repl.py:894 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:918 +#: bpython/repl.py:899 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:956 +#: bpython/repl.py:937 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:964 bpython/repl.py:968 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:971 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1147 +#: bpython/repl.py:1128 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1175 +#: bpython/repl.py:1153 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1181 +#: bpython/repl.py:1158 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:604 +#: bpython/urwid.py:606 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -#: bpython/urwid.py:1114 +#: bpython/urwid.py:1116 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1119 +#: bpython/urwid.py:1121 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1127 +#: bpython/urwid.py:1129 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1132 +#: bpython/urwid.py:1134 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1143 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1335 +#: bpython/urwid.py:1337 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:325 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:327 +#: bpython/curtsiesfrontend/repl.py:341 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:664 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:682 +#: bpython/curtsiesfrontend/repl.py:698 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:991 +#: bpython/curtsiesfrontend/repl.py:1008 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1044 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1050 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1055 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1061 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1986 +#: bpython/curtsiesfrontend/repl.py:2011 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 718acbb7f..79b3acf71 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -7,116 +7,118 @@ msgid "" msgstr "" "Project-Id-Version: bpython mercurial\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-02-14 17:29+0100\n" +"POT-Creation-Date: 2021-10-12 21:58+0200\n" "PO-Revision-Date: 2021-02-14 17:31+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: de\n" "Language-Team: de \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -"X-Generator: Poedit 2.4.2\n" -#: bpython/args.py:49 +#: bpython/args.py:63 msgid "{} version {} on top of Python {} {}" msgstr "{} Version {} mit Python {} {}" -#: bpython/args.py:58 +#: bpython/args.py:72 msgid "{} See AUTHORS.rst for details." msgstr "{} Siehe AUTHORS.rst für mehr Details." -#: bpython/args.py:95 +#: bpython/args.py:116 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" -"NOTE: If bpython sees an argument it does not know, execution falls back to the " -"regular Python interpreter." +"NOTE: If bpython sees an argument it does not know, execution falls back " +"to the regular Python interpreter." msgstr "" "Verwendung: %(prog)s [Optionen] [Datei [Argumente]]\n" -"Hinweis: Wenn bpython Argumente übergeben bekommt, die nicht verstanden werden, " -"wird der normale Python Interpreter ausgeführt." +"Hinweis: Wenn bpython Argumente übergeben bekommt, die nicht verstanden " +"werden, wird der normale Python Interpreter ausgeführt." -#: bpython/args.py:105 +#: bpython/args.py:127 msgid "Use CONFIG instead of default config file." msgstr "Verwende CONFIG antatt der standardmäßigen Konfigurationsdatei." -#: bpython/args.py:111 +#: bpython/args.py:133 msgid "Drop to bpython shell after running file instead of exiting." msgstr "Verbleibe in bpython nach dem Ausführen der Datei." -#: bpython/args.py:117 +#: bpython/args.py:139 msgid "Don't flush the output to stdout." msgstr "Gib Ausgabe beim Beenden nicht ernaut auf stdout aus." -#: bpython/args.py:123 +#: bpython/args.py:145 msgid "Print version and exit." msgstr "Zeige Versionsinformationen an und beende." -#: bpython/args.py:130 +#: bpython/args.py:152 msgid "Set log level for logging" msgstr "Log-Stufe" -#: bpython/args.py:135 +#: bpython/args.py:157 msgid "Log output file" msgstr "Datei für Ausgabe von Log-Nachrichten" -#: bpython/args.py:146 +#: bpython/args.py:168 msgid "File to execute and additional arguments passed on to the executed script." -msgstr "Auszuführende Datei und zusätzliche Argumente, die an das Script übergeben werden sollen." +msgstr "" +"Auszuführende Datei und zusätzliche Argumente, die an das Script " +"übergeben werden sollen." -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 +#: bpython/urwid.py:539 msgid "y" msgstr "j" -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/urwid.py:539 msgid "yes" msgstr "ja" -#: bpython/cli.py:1692 +#: bpython/cli.py:1696 msgid "Rewind" msgstr "Rückgängig" -#: bpython/cli.py:1693 +#: bpython/cli.py:1697 msgid "Save" msgstr "Speichern" -#: bpython/cli.py:1694 +#: bpython/cli.py:1698 msgid "Pastebin" msgstr "Pastebin" -#: bpython/cli.py:1695 +#: bpython/cli.py:1699 msgid "Pager" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1700 msgid "Show Source" msgstr "Quellcode anzeigen" -#: bpython/cli.py:1943 +#: bpython/cli.py:1947 msgid "" -"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. This " -"backend has been deprecated in version 0.19 and might disappear in a future " -"version." +"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." msgstr "" -"ACHTUNG: `bpython-cli` wird verwendet, die curses Implementierung von `bpython`. " -"Diese Implementierung wird ab Version 0.19 nicht mehr aktiv unterstützt und wird " -"in einer zukünftigen Version entfernt werden." +"ACHTUNG: `bpython-cli` wird verwendet, die curses Implementierung von " +"`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " +"unterstützt und wird in einer zukünftigen Version entfernt werden." -#: bpython/curtsies.py:136 +#: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:142 +#: bpython/curtsies.py:207 msgid "curtsies arguments" msgstr "Argumente für curtsies" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:208 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "Zusätzliche Argumente spezifisch für die curtsies-basierte REPL." -#: bpython/history.py:224 +#: bpython/history.py:250 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Fehler beim Schreiben in Datei %s aufgetreten (%s)" @@ -142,249 +144,255 @@ msgstr "Keine Ausgabe von Hilfsprogramm vorhanden." msgid "Failed to recognize the helper program's output as an URL." msgstr "Konnte Ausgabe von Hilfsprogramm nicht verarbeiten." -#: bpython/repl.py:653 +#: bpython/repl.py:644 msgid "Nothing to get source of" msgstr "Nichts um Quellcode abzurufen" -#: bpython/repl.py:658 +#: bpython/repl.py:649 #, python-format msgid "Cannot get source: %s" msgstr "Kann Quellcode nicht finden: %s" -#: bpython/repl.py:663 +#: bpython/repl.py:654 #, python-format msgid "Cannot access source of %r" msgstr "Kann auf Quellcode nicht zugreifen: %r" -#: bpython/repl.py:665 +#: bpython/repl.py:656 #, python-format msgid "No source code found for %s" msgstr "Quellcode für %s nicht gefunden" -#: bpython/repl.py:820 +#: bpython/repl.py:801 msgid "Save to file (Esc to cancel): " msgstr "In Datei speichern (Esc um abzubrechen): " -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:803 bpython/repl.py:806 bpython/repl.py:830 msgid "Save cancelled." msgstr "Speichern abgebrochen." -#: bpython/repl.py:836 +#: bpython/repl.py:817 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "%s existiert bereit. (C) abbrechen, (o) überschrieben oder (a) anhängen? " -#: bpython/repl.py:844 +#: bpython/repl.py:825 msgid "overwrite" msgstr "überschreiben" -#: bpython/repl.py:846 +#: bpython/repl.py:827 msgid "append" msgstr "anhängen" -#: bpython/repl.py:858 bpython/repl.py:1165 +#: bpython/repl.py:839 bpython/repl.py:1143 #, python-format msgid "Error writing file '%s': %s" msgstr "Fehler beim Schreiben in Datei '%s': %s" -#: bpython/repl.py:860 +#: bpython/repl.py:841 #, python-format msgid "Saved to %s." msgstr "Nach %s gespeichert." -#: bpython/repl.py:866 +#: bpython/repl.py:847 msgid "No clipboard available." msgstr "Zwischenablage ist nicht verfügbar." -#: bpython/repl.py:873 +#: bpython/repl.py:854 msgid "Could not copy to clipboard." msgstr "Konnte nicht in Zwischenablage kopieren." -#: bpython/repl.py:875 +#: bpython/repl.py:856 msgid "Copied content to clipboard." msgstr "Inhalt wurde in Zwischenablage kopiert." -#: bpython/repl.py:884 +#: bpython/repl.py:865 msgid "Pastebin buffer? (y/N) " msgstr "Buffer bei Pastebin hochladen? (j/N)" -#: bpython/repl.py:886 +#: bpython/repl.py:867 msgid "Pastebin aborted." msgstr "Hochladen zu Pastebin abgebrochen." -#: bpython/repl.py:894 +#: bpython/repl.py:875 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" -msgstr "Duplizierte Daten zu Pastebin hochgeladen. Vorherige URL: %s. URL zum Löschen: %s" +msgstr "" +"Duplizierte Daten zu Pastebin hochgeladen. Vorherige URL: %s. URL zum " +"Löschen: %s" -#: bpython/repl.py:900 +#: bpython/repl.py:881 msgid "Posting data to pastebin..." msgstr "Lade Daten hoch zu Pastebin..." -#: bpython/repl.py:904 +#: bpython/repl.py:885 #, python-format msgid "Upload failed: %s" msgstr "Hochladen ist fehlgeschlagen: %s" -#: bpython/repl.py:913 +#: bpython/repl.py:894 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "Pastebin URL: %s - URL zum Löschen: %s" -#: bpython/repl.py:918 +#: bpython/repl.py:899 #, python-format msgid "Pastebin URL: %s" msgstr "Pastebin URL: %s" -#: bpython/repl.py:956 +#: bpython/repl.py:937 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -"Wie viele Zeilen rückgängig machen? (Rückgängigmachen wird bis zu ~%.1f Sekunden " -"brauchen) [1]" +"Wie viele Zeilen rückgängig machen? (Rückgängigmachen wird bis zu ~%.1f " +"Sekunden brauchen) [1]" -#: bpython/repl.py:964 bpython/repl.py:968 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "Rückgängigmachen abgebrochen" -#: bpython/repl.py:971 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "Mache %d Zeile rückgängig... (ungefähr %.1f Sekunden)" msgstr[1] "Mache %d Zeilen rückgängig... (ungefähr %.1f Sekunden)" -#: bpython/repl.py:1147 +#: bpython/repl.py:1128 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt werden? (j/N)" +"Konfigurationsdatei existiert nicht. Soll eine neue Datei erstellt " +"werden? (j/N)" -#: bpython/repl.py:1175 +#: bpython/repl.py:1153 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -"bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die Änderungen " -"übernommen werden." +"bpython Konfigurationsdatei bearbeitet. Starte bpython neu damit die " +"Änderungen übernommen werden." -#: bpython/repl.py:1181 +#: bpython/repl.py:1158 #, python-format msgid "Error editing config file: %s" msgstr "Fehler beim Bearbeiten der Konfigurationsdatei: %s" -#: bpython/urwid.py:604 +#: bpython/urwid.py:606 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" -" <%s> Rückgängigmachen <%s> Speichern <%s> Pastebin <%s> Pager <%s> Quellcode " -"anzeigen " +" <%s> Rückgängigmachen <%s> Speichern <%s> Pastebin <%s> Pager <%s> " +"Quellcode anzeigen " -#: bpython/urwid.py:1114 +#: bpython/urwid.py:1116 msgid "Run twisted reactor." msgstr "Führe twisted reactor aus." -#: bpython/urwid.py:1119 +#: bpython/urwid.py:1121 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "Wähle reactor aus (siehe --help-reactors). Impliziert --twisted." -#: bpython/urwid.py:1127 +#: bpython/urwid.py:1129 msgid "List available reactors for -r." msgstr "Liste verfügbare reactors für -r auf." -#: bpython/urwid.py:1132 +#: bpython/urwid.py:1134 msgid "" -"twistd plugin to run (use twistd for a list). Use \"--\" to pass further options " -"to the plugin." +"twistd plugin to run (use twistd for a list). Use \"--\" to pass further " +"options to the plugin." msgstr "" -"Auszuführendes twistd Plugin (starte twistd für eine Liste). Verwende \"--\" um " -"Optionen an das Plugin zu übergeben." +"Auszuführendes twistd Plugin (starte twistd für eine Liste). Verwende " +"\"--\" um Optionen an das Plugin zu übergeben." -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1143 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1335 +#: bpython/urwid.py:1337 msgid "" -"WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. This " -"backend has been deprecated in version 0.19 and might disappear in a future " -"version." +"WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " +"This backend has been deprecated in version 0.19 and might disappear in a" +" future version." msgstr "" "ACHTUNG: `bpython-urwid` wird verwendet, die curses Implementierung von " "`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " "unterstützt und wird in einer zukünftigen Version entfernt werden." -#: bpython/curtsiesfrontend/repl.py:325 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "Willkommen by bpython!" -#: bpython/curtsiesfrontend/repl.py:327 +#: bpython/curtsiesfrontend/repl.py:341 #, python-format msgid "Press <%s> for help." msgstr "Drücke <%s> für Hilfe." -#: bpython/curtsiesfrontend/repl.py:664 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "Fehler beim Ausführen von PYTHONSTARTUP: %s" -#: bpython/curtsiesfrontend/repl.py:682 +#: bpython/curtsiesfrontend/repl.py:698 #, python-format msgid "Reloaded at %s because %s modified." msgstr "Bei %s neugeladen, da %s modifiziert wurde." -#: bpython/curtsiesfrontend/repl.py:991 +#: bpython/curtsiesfrontend/repl.py:1008 msgid "Session not reevaluated because it was not edited" msgstr "Die Sitzung wurde nicht neu ausgeführt, da sie nicht berabeitet wurde" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session not reevaluated because saved file was blank" msgstr "Die Sitzung wurde nicht neu ausgeführt, da die gespeicherte Datei leer war" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Session edited and reevaluated" msgstr "Sitzung bearbeitet und neu ausgeführt" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1044 #, python-format msgid "Reloaded at %s by user." msgstr "Bei %s vom Benutzer neu geladen." -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1050 msgid "Auto-reloading deactivated." msgstr "Automatisches Neuladen deaktiviert." -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1055 msgid "Auto-reloading active, watching for file changes..." msgstr "Automatisches Neuladen ist aktiv; beobachte Dateiänderungen..." -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1061 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -"Automatisches Neuladen ist nicht verfügbar da watchdog nicht installiert ist." +"Automatisches Neuladen ist nicht verfügbar da watchdog nicht installiert " +"ist." -#: bpython/curtsiesfrontend/repl.py:1986 +#: bpython/curtsiesfrontend/repl.py:2011 msgid "" "\n" "Thanks for using bpython!\n" "\n" -"See http://bpython-interpreter.org/ for more information and http://docs.bpython-" -"interpreter.org/ for docs.\n" +"See http://bpython-interpreter.org/ for more information and http://docs" +".bpython-interpreter.org/ for docs.\n" "Please report issues at https://github.com/bpython/bpython/issues\n" "\n" "Features:\n" "Try using undo ({config.undo_key})!\n" -"Edit the current line ({config.edit_current_block_key}) or the entire session " -"({config.external_editor_key}) in an external editor. (currently {config." -"editor})\n" -"Save sessions ({config.save_key}) or post them to pastebins ({config." -"pastebin_key})! Current pastebin helper: {config.pastebin_helper}\n" -"Reload all modules and rerun session ({config.reimport_key}) to test out changes " -"to a module.\n" -"Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute the " -"current session when a module you've imported is modified.\n" +"Edit the current line ({config.edit_current_block_key}) or the entire " +"session ({config.external_editor_key}) in an external editor. (currently " +"{config.editor})\n" +"Save sessions ({config.save_key}) or post them to pastebins " +"({config.pastebin_key})! Current pastebin helper: " +"{config.pastebin_helper}\n" +"Reload all modules and rerun session ({config.reimport_key}) to test out " +"changes to a module.\n" +"Toggle auto-reload mode ({config.toggle_file_watch_key}) to re-execute " +"the current session when a module you've imported is modified.\n" "\n" "bpython -i your_script.py runs a file in interactive mode\n" "bpython -t your_script.py pastes the contents of a file into the session\n" "\n" -"A config file at {config.config_path} customizes keys and behavior of bpython.\n" +"A config file at {config.config_path} customizes keys and behavior of " +"bpython.\n" "You can also set which pastebin helper and which external editor to use.\n" "See {example_config_url} for an example config file.\n" "Press {config.edit_config_key} to edit this config file.\n" diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index b877fd1bd..5af25b37a 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-02-14 17:29+0100\n" +"POT-Creation-Date: 2021-10-12 21:58+0200\n" "PO-Revision-Date: 2020-10-29 12:22+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: es_ES\n" @@ -18,15 +18,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:49 +#: bpython/args.py:63 msgid "{} version {} on top of Python {} {}" msgstr "" -#: bpython/args.py:58 +#: bpython/args.py:72 msgid "{} See AUTHORS.rst for details." msgstr "" -#: bpython/args.py:95 +#: bpython/args.py:116 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -34,82 +34,83 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:105 +#: bpython/args.py:127 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:111 +#: bpython/args.py:133 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:117 +#: bpython/args.py:139 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:123 +#: bpython/args.py:145 msgid "Print version and exit." msgstr "" -#: bpython/args.py:130 +#: bpython/args.py:152 msgid "Set log level for logging" msgstr "" -#: bpython/args.py:135 +#: bpython/args.py:157 msgid "Log output file" msgstr "" -#: bpython/args.py:146 +#: bpython/args.py:168 msgid "File to execute and additional arguments passed on to the executed script." msgstr "" -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 +#: bpython/urwid.py:539 msgid "y" msgstr "s" -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/urwid.py:539 msgid "yes" msgstr "si" -#: bpython/cli.py:1692 +#: bpython/cli.py:1696 msgid "Rewind" msgstr "" -#: bpython/cli.py:1693 +#: bpython/cli.py:1697 msgid "Save" msgstr "" -#: bpython/cli.py:1694 +#: bpython/cli.py:1698 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1695 +#: bpython/cli.py:1699 msgid "Pager" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1700 msgid "Show Source" msgstr "" -#: bpython/cli.py:1943 +#: bpython/cli.py:1947 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsies.py:136 +#: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:142 +#: bpython/curtsies.py:207 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:208 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" -#: bpython/history.py:224 +#: bpython/history.py:250 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" @@ -135,214 +136,214 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:653 +#: bpython/repl.py:644 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:649 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:663 +#: bpython/repl.py:654 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:665 +#: bpython/repl.py:656 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:820 +#: bpython/repl.py:801 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:803 bpython/repl.py:806 bpython/repl.py:830 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:817 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:844 +#: bpython/repl.py:825 msgid "overwrite" msgstr "" -#: bpython/repl.py:846 +#: bpython/repl.py:827 msgid "append" msgstr "" -#: bpython/repl.py:858 bpython/repl.py:1165 +#: bpython/repl.py:839 bpython/repl.py:1143 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:860 +#: bpython/repl.py:841 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:866 +#: bpython/repl.py:847 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:873 +#: bpython/repl.py:854 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:856 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:884 +#: bpython/repl.py:865 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:867 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:875 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:881 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:885 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:913 +#: bpython/repl.py:894 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:918 +#: bpython/repl.py:899 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:956 +#: bpython/repl.py:937 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:964 bpython/repl.py:968 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:971 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1147 +#: bpython/repl.py:1128 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1175 +#: bpython/repl.py:1153 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1181 +#: bpython/repl.py:1158 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:604 +#: bpython/urwid.py:606 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra el " "código fuente" -#: bpython/urwid.py:1114 +#: bpython/urwid.py:1116 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1119 +#: bpython/urwid.py:1121 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1127 +#: bpython/urwid.py:1129 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1132 +#: bpython/urwid.py:1134 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1143 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1335 +#: bpython/urwid.py:1337 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:325 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:327 +#: bpython/curtsiesfrontend/repl.py:341 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:664 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:682 +#: bpython/curtsiesfrontend/repl.py:698 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:991 +#: bpython/curtsiesfrontend/repl.py:1008 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1044 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1050 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1055 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1061 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1986 +#: bpython/curtsiesfrontend/repl.py:2011 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index d4c4d7758..32bbec662 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.13-442\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-02-14 17:29+0100\n" +"POT-Creation-Date: 2021-10-12 21:58+0200\n" "PO-Revision-Date: 2020-10-29 12:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: fr_FR\n" @@ -17,15 +17,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:49 +#: bpython/args.py:63 msgid "{} version {} on top of Python {} {}" msgstr "" -#: bpython/args.py:58 +#: bpython/args.py:72 msgid "{} See AUTHORS.rst for details." msgstr "" -#: bpython/args.py:95 +#: bpython/args.py:116 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -36,84 +36,85 @@ msgstr "" "NOTE: Si bpython ne reconnaît pas un des arguments fournis, " "l'interpréteur Python classique sera lancé" -#: bpython/args.py:105 +#: bpython/args.py:127 msgid "Use CONFIG instead of default config file." msgstr "Utiliser CONFIG à la place du fichier de configuration par défaut." -#: bpython/args.py:111 +#: bpython/args.py:133 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" "Aller dans le shell bpython après l'exécution du fichier au lieu de " "quitter." -#: bpython/args.py:117 +#: bpython/args.py:139 msgid "Don't flush the output to stdout." msgstr "Ne pas purger la sortie vers stdout." -#: bpython/args.py:123 +#: bpython/args.py:145 msgid "Print version and exit." msgstr "Afficher la version et quitter." -#: bpython/args.py:130 +#: bpython/args.py:152 msgid "Set log level for logging" msgstr "" -#: bpython/args.py:135 +#: bpython/args.py:157 msgid "Log output file" msgstr "" -#: bpython/args.py:146 +#: bpython/args.py:168 msgid "File to execute and additional arguments passed on to the executed script." msgstr "" -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 +#: bpython/urwid.py:539 msgid "y" msgstr "o" -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/urwid.py:539 msgid "yes" msgstr "oui" -#: bpython/cli.py:1692 +#: bpython/cli.py:1696 msgid "Rewind" msgstr "Rembobiner" -#: bpython/cli.py:1693 +#: bpython/cli.py:1697 msgid "Save" msgstr "Sauvegarder" -#: bpython/cli.py:1694 +#: bpython/cli.py:1698 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1695 +#: bpython/cli.py:1699 msgid "Pager" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1700 msgid "Show Source" msgstr "Montrer le code source" -#: bpython/cli.py:1943 +#: bpython/cli.py:1947 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsies.py:136 +#: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:142 +#: bpython/curtsies.py:207 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:208 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" -#: bpython/history.py:224 +#: bpython/history.py:250 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "Une erreur s'est produite pendant l'écriture du fichier %s (%s)" @@ -139,149 +140,149 @@ msgstr "pas de sortie du programme externe." msgid "Failed to recognize the helper program's output as an URL." msgstr "la sortie du programme externe ne correspond pas à une URL." -#: bpython/repl.py:653 +#: bpython/repl.py:644 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:649 #, python-format msgid "Cannot get source: %s" msgstr "Impossible de récupérer le source: %s" -#: bpython/repl.py:663 +#: bpython/repl.py:654 #, python-format msgid "Cannot access source of %r" msgstr "Impossible d'accéder au source de %r" -#: bpython/repl.py:665 +#: bpython/repl.py:656 #, python-format msgid "No source code found for %s" msgstr "Pas de code source trouvé pour %s" -#: bpython/repl.py:820 +#: bpython/repl.py:801 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:803 bpython/repl.py:806 bpython/repl.py:830 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:817 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:844 +#: bpython/repl.py:825 msgid "overwrite" msgstr "" -#: bpython/repl.py:846 +#: bpython/repl.py:827 msgid "append" msgstr "" -#: bpython/repl.py:858 bpython/repl.py:1165 +#: bpython/repl.py:839 bpython/repl.py:1143 #, python-format msgid "Error writing file '%s': %s" msgstr "Une erreur s'est produite pendant l'écriture du fichier '%s': %s" -#: bpython/repl.py:860 +#: bpython/repl.py:841 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:866 +#: bpython/repl.py:847 msgid "No clipboard available." msgstr "Pas de presse-papier disponible." -#: bpython/repl.py:873 +#: bpython/repl.py:854 msgid "Could not copy to clipboard." msgstr "Impossible de copier vers le presse-papier." -#: bpython/repl.py:875 +#: bpython/repl.py:856 msgid "Copied content to clipboard." msgstr "Contenu copié vers le presse-papier." -#: bpython/repl.py:884 +#: bpython/repl.py:865 msgid "Pastebin buffer? (y/N) " msgstr "Tampon Pastebin ? (o/N) " -#: bpython/repl.py:886 +#: bpython/repl.py:867 msgid "Pastebin aborted." msgstr "Pastebin abandonné." -#: bpython/repl.py:894 +#: bpython/repl.py:875 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "Pastebin dupliqué. URL précédente: %s. URL de suppression: %s" -#: bpython/repl.py:900 +#: bpython/repl.py:881 msgid "Posting data to pastebin..." msgstr "Envoi des donnés à pastebin..." -#: bpython/repl.py:904 +#: bpython/repl.py:885 #, python-format msgid "Upload failed: %s" msgstr "Echec du téléchargement: %s" -#: bpython/repl.py:913 +#: bpython/repl.py:894 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "URL Pastebin: %s - URL de suppression: %s" -#: bpython/repl.py:918 +#: bpython/repl.py:899 #, python-format msgid "Pastebin URL: %s" msgstr "URL Pastebin: %s" -#: bpython/repl.py:956 +#: bpython/repl.py:937 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:964 bpython/repl.py:968 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:971 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1147 +#: bpython/repl.py:1128 msgid "Config file does not exist - create new from default? (y/N)" msgstr "Le fichier de configuration n'existe pas - en créér un par défaut? (o/N)" -#: bpython/repl.py:1175 +#: bpython/repl.py:1153 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1181 +#: bpython/repl.py:1158 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:604 +#: bpython/urwid.py:606 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr "" " <%s> Rebobiner <%s> Sauvegarder <%s> Pastebin <%s> Pager <%s> " "Montrer Source " -#: bpython/urwid.py:1114 +#: bpython/urwid.py:1116 msgid "Run twisted reactor." msgstr "Lancer le reactor twisted." -#: bpython/urwid.py:1119 +#: bpython/urwid.py:1121 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "Choisir un reactor spécifique (voir --help-reactors). Nécessite --twisted." -#: bpython/urwid.py:1127 +#: bpython/urwid.py:1129 msgid "List available reactors for -r." msgstr "Lister les reactors disponibles pour -r." -#: bpython/urwid.py:1132 +#: bpython/urwid.py:1134 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." @@ -289,66 +290,66 @@ msgstr "" "plugin twistd à lancer (utiliser twistd pour une list). Utiliser \"--\" " "pour donner plus d'options au plugin." -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1143 msgid "Port to run an eval server on (forces Twisted)." msgstr "Port pour lancer un server eval (force Twisted)." -#: bpython/urwid.py:1335 +#: bpython/urwid.py:1337 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:325 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "Bienvenue dans bpython!" -#: bpython/curtsiesfrontend/repl.py:327 +#: bpython/curtsiesfrontend/repl.py:341 #, python-format msgid "Press <%s> for help." msgstr "Appuyer sur <%s> pour de l'aide." -#: bpython/curtsiesfrontend/repl.py:664 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "L'exécution de PYTHONSTARTUP a échoué: %s" -#: bpython/curtsiesfrontend/repl.py:682 +#: bpython/curtsiesfrontend/repl.py:698 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:991 +#: bpython/curtsiesfrontend/repl.py:1008 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1044 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1050 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1055 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1061 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1986 +#: bpython/curtsiesfrontend/repl.py:2011 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index ddb93cc0c..d0076cffd 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-02-14 17:29+0100\n" +"POT-Creation-Date: 2021-10-12 21:58+0200\n" "PO-Revision-Date: 2015-02-02 00:34+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: it_IT\n" @@ -18,15 +18,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:49 +#: bpython/args.py:63 msgid "{} version {} on top of Python {} {}" msgstr "" -#: bpython/args.py:58 +#: bpython/args.py:72 msgid "{} See AUTHORS.rst for details." msgstr "" -#: bpython/args.py:95 +#: bpython/args.py:116 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -34,82 +34,83 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:105 +#: bpython/args.py:127 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:111 +#: bpython/args.py:133 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:117 +#: bpython/args.py:139 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:123 +#: bpython/args.py:145 msgid "Print version and exit." msgstr "" -#: bpython/args.py:130 +#: bpython/args.py:152 msgid "Set log level for logging" msgstr "" -#: bpython/args.py:135 +#: bpython/args.py:157 msgid "Log output file" msgstr "" -#: bpython/args.py:146 +#: bpython/args.py:168 msgid "File to execute and additional arguments passed on to the executed script." msgstr "" -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 +#: bpython/urwid.py:539 msgid "y" msgstr "s" -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/urwid.py:539 msgid "yes" msgstr "si" -#: bpython/cli.py:1692 +#: bpython/cli.py:1696 msgid "Rewind" msgstr "" -#: bpython/cli.py:1693 +#: bpython/cli.py:1697 msgid "Save" msgstr "" -#: bpython/cli.py:1694 +#: bpython/cli.py:1698 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1695 +#: bpython/cli.py:1699 msgid "Pager" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1700 msgid "Show Source" msgstr "" -#: bpython/cli.py:1943 +#: bpython/cli.py:1947 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsies.py:136 +#: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:142 +#: bpython/curtsies.py:207 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:208 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" -#: bpython/history.py:224 +#: bpython/history.py:250 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" @@ -135,212 +136,212 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:653 +#: bpython/repl.py:644 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:649 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:663 +#: bpython/repl.py:654 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:665 +#: bpython/repl.py:656 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:820 +#: bpython/repl.py:801 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:803 bpython/repl.py:806 bpython/repl.py:830 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:817 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:844 +#: bpython/repl.py:825 msgid "overwrite" msgstr "" -#: bpython/repl.py:846 +#: bpython/repl.py:827 msgid "append" msgstr "" -#: bpython/repl.py:858 bpython/repl.py:1165 +#: bpython/repl.py:839 bpython/repl.py:1143 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:860 +#: bpython/repl.py:841 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:866 +#: bpython/repl.py:847 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:873 +#: bpython/repl.py:854 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:856 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:884 +#: bpython/repl.py:865 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:867 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:875 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:881 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:885 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:913 +#: bpython/repl.py:894 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:918 +#: bpython/repl.py:899 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:956 +#: bpython/repl.py:937 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:964 bpython/repl.py:968 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:971 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1147 +#: bpython/repl.py:1128 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1175 +#: bpython/repl.py:1153 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1181 +#: bpython/repl.py:1158 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:604 +#: bpython/urwid.py:606 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Salva <%s> Pastebin <%s> Pager <%s> Mostra Sorgente" -#: bpython/urwid.py:1114 +#: bpython/urwid.py:1116 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1119 +#: bpython/urwid.py:1121 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1127 +#: bpython/urwid.py:1129 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1132 +#: bpython/urwid.py:1134 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1143 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1335 +#: bpython/urwid.py:1337 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:325 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:327 +#: bpython/curtsiesfrontend/repl.py:341 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:664 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:682 +#: bpython/curtsiesfrontend/repl.py:698 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:991 +#: bpython/curtsiesfrontend/repl.py:1008 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1044 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1050 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1055 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1061 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1986 +#: bpython/curtsiesfrontend/repl.py:2011 msgid "" "\n" "Thanks for using bpython!\n" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index 582442406..d110f3ba8 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: bpython 0.9.7.1\n" "Report-Msgid-Bugs-To: http://github.com/bpython/bpython/issues\n" -"POT-Creation-Date: 2021-02-14 17:29+0100\n" +"POT-Creation-Date: 2021-10-12 21:58+0200\n" "PO-Revision-Date: 2020-10-29 12:20+0100\n" "Last-Translator: Sebastian Ramacher \n" "Language: nl_NL\n" @@ -18,15 +18,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.8.0\n" -#: bpython/args.py:49 +#: bpython/args.py:63 msgid "{} version {} on top of Python {} {}" msgstr "" -#: bpython/args.py:58 +#: bpython/args.py:72 msgid "{} See AUTHORS.rst for details." msgstr "" -#: bpython/args.py:95 +#: bpython/args.py:116 #, python-format msgid "" "Usage: %(prog)s [options] [file [args]]\n" @@ -34,82 +34,83 @@ msgid "" "to the regular Python interpreter." msgstr "" -#: bpython/args.py:105 +#: bpython/args.py:127 msgid "Use CONFIG instead of default config file." msgstr "" -#: bpython/args.py:111 +#: bpython/args.py:133 msgid "Drop to bpython shell after running file instead of exiting." msgstr "" -#: bpython/args.py:117 +#: bpython/args.py:139 msgid "Don't flush the output to stdout." msgstr "" -#: bpython/args.py:123 +#: bpython/args.py:145 msgid "Print version and exit." msgstr "" -#: bpython/args.py:130 +#: bpython/args.py:152 msgid "Set log level for logging" msgstr "" -#: bpython/args.py:135 +#: bpython/args.py:157 msgid "Log output file" msgstr "" -#: bpython/args.py:146 +#: bpython/args.py:168 msgid "File to execute and additional arguments passed on to the executed script." msgstr "" -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 +#: bpython/urwid.py:539 msgid "y" msgstr "j" -#: bpython/cli.py:312 bpython/urwid.py:537 +#: bpython/cli.py:320 bpython/urwid.py:539 msgid "yes" msgstr "ja" -#: bpython/cli.py:1692 +#: bpython/cli.py:1696 msgid "Rewind" msgstr "" -#: bpython/cli.py:1693 +#: bpython/cli.py:1697 msgid "Save" msgstr "" -#: bpython/cli.py:1694 +#: bpython/cli.py:1698 msgid "Pastebin" msgstr "" -#: bpython/cli.py:1695 +#: bpython/cli.py:1699 msgid "Pager" msgstr "" -#: bpython/cli.py:1696 +#: bpython/cli.py:1700 msgid "Show Source" msgstr "" -#: bpython/cli.py:1943 +#: bpython/cli.py:1947 msgid "" "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsies.py:136 +#: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" -#: bpython/curtsies.py:142 +#: bpython/curtsies.py:207 msgid "curtsies arguments" msgstr "" -#: bpython/curtsies.py:143 +#: bpython/curtsies.py:208 msgid "Additional arguments specific to the curtsies-based REPL." msgstr "" -#: bpython/history.py:224 +#: bpython/history.py:250 #, python-format msgid "Error occurred while writing to file %s (%s)" msgstr "" @@ -135,212 +136,212 @@ msgstr "" msgid "Failed to recognize the helper program's output as an URL." msgstr "" -#: bpython/repl.py:653 +#: bpython/repl.py:644 msgid "Nothing to get source of" msgstr "" -#: bpython/repl.py:658 +#: bpython/repl.py:649 #, python-format msgid "Cannot get source: %s" msgstr "" -#: bpython/repl.py:663 +#: bpython/repl.py:654 #, python-format msgid "Cannot access source of %r" msgstr "" -#: bpython/repl.py:665 +#: bpython/repl.py:656 #, python-format msgid "No source code found for %s" msgstr "" -#: bpython/repl.py:820 +#: bpython/repl.py:801 msgid "Save to file (Esc to cancel): " msgstr "" -#: bpython/repl.py:822 bpython/repl.py:825 bpython/repl.py:849 +#: bpython/repl.py:803 bpython/repl.py:806 bpython/repl.py:830 msgid "Save cancelled." msgstr "" -#: bpython/repl.py:836 +#: bpython/repl.py:817 #, python-format msgid "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " msgstr "" -#: bpython/repl.py:844 +#: bpython/repl.py:825 msgid "overwrite" msgstr "" -#: bpython/repl.py:846 +#: bpython/repl.py:827 msgid "append" msgstr "" -#: bpython/repl.py:858 bpython/repl.py:1165 +#: bpython/repl.py:839 bpython/repl.py:1143 #, python-format msgid "Error writing file '%s': %s" msgstr "" -#: bpython/repl.py:860 +#: bpython/repl.py:841 #, python-format msgid "Saved to %s." msgstr "" -#: bpython/repl.py:866 +#: bpython/repl.py:847 msgid "No clipboard available." msgstr "" -#: bpython/repl.py:873 +#: bpython/repl.py:854 msgid "Could not copy to clipboard." msgstr "" -#: bpython/repl.py:875 +#: bpython/repl.py:856 msgid "Copied content to clipboard." msgstr "" -#: bpython/repl.py:884 +#: bpython/repl.py:865 msgid "Pastebin buffer? (y/N) " msgstr "" -#: bpython/repl.py:886 +#: bpython/repl.py:867 msgid "Pastebin aborted." msgstr "" -#: bpython/repl.py:894 +#: bpython/repl.py:875 #, python-format msgid "Duplicate pastebin. Previous URL: %s. Removal URL: %s" msgstr "" -#: bpython/repl.py:900 +#: bpython/repl.py:881 msgid "Posting data to pastebin..." msgstr "" -#: bpython/repl.py:904 +#: bpython/repl.py:885 #, python-format msgid "Upload failed: %s" msgstr "" -#: bpython/repl.py:913 +#: bpython/repl.py:894 #, python-format msgid "Pastebin URL: %s - Removal URL: %s" msgstr "" -#: bpython/repl.py:918 +#: bpython/repl.py:899 #, python-format msgid "Pastebin URL: %s" msgstr "" -#: bpython/repl.py:956 +#: bpython/repl.py:937 #, python-format msgid "Undo how many lines? (Undo will take up to ~%.1f seconds) [1]" msgstr "" -#: bpython/repl.py:964 bpython/repl.py:968 +#: bpython/repl.py:945 bpython/repl.py:949 msgid "Undo canceled" msgstr "" -#: bpython/repl.py:971 +#: bpython/repl.py:952 #, python-format msgid "Undoing %d line... (est. %.1f seconds)" msgid_plural "Undoing %d lines... (est. %.1f seconds)" msgstr[0] "" msgstr[1] "" -#: bpython/repl.py:1147 +#: bpython/repl.py:1128 msgid "Config file does not exist - create new from default? (y/N)" msgstr "" -#: bpython/repl.py:1175 +#: bpython/repl.py:1153 msgid "bpython config file edited. Restart bpython for changes to take effect." msgstr "" -#: bpython/repl.py:1181 +#: bpython/repl.py:1158 #, python-format msgid "Error editing config file: %s" msgstr "" -#: bpython/urwid.py:604 +#: bpython/urwid.py:606 #, python-format msgid " <%s> Rewind <%s> Save <%s> Pastebin <%s> Pager <%s> Show Source " msgstr " <%s> Rewind <%s> Opslaan <%s> Pastebin <%s> Pager <%s> Toon broncode" -#: bpython/urwid.py:1114 +#: bpython/urwid.py:1116 msgid "Run twisted reactor." msgstr "" -#: bpython/urwid.py:1119 +#: bpython/urwid.py:1121 msgid "Select specific reactor (see --help-reactors). Implies --twisted." msgstr "" -#: bpython/urwid.py:1127 +#: bpython/urwid.py:1129 msgid "List available reactors for -r." msgstr "" -#: bpython/urwid.py:1132 +#: bpython/urwid.py:1134 msgid "" "twistd plugin to run (use twistd for a list). Use \"--\" to pass further " "options to the plugin." msgstr "" -#: bpython/urwid.py:1141 +#: bpython/urwid.py:1143 msgid "Port to run an eval server on (forces Twisted)." msgstr "" -#: bpython/urwid.py:1335 +#: bpython/urwid.py:1337 msgid "" "WARNING: You are using `bpython-urwid`, the urwid backend for `bpython`. " "This backend has been deprecated in version 0.19 and might disappear in a" " future version." msgstr "" -#: bpython/curtsiesfrontend/repl.py:325 +#: bpython/curtsiesfrontend/repl.py:339 msgid "Welcome to bpython!" msgstr "" -#: bpython/curtsiesfrontend/repl.py:327 +#: bpython/curtsiesfrontend/repl.py:341 #, python-format msgid "Press <%s> for help." msgstr "" -#: bpython/curtsiesfrontend/repl.py:664 +#: bpython/curtsiesfrontend/repl.py:681 #, python-format msgid "Executing PYTHONSTARTUP failed: %s" msgstr "" -#: bpython/curtsiesfrontend/repl.py:682 +#: bpython/curtsiesfrontend/repl.py:698 #, python-format msgid "Reloaded at %s because %s modified." msgstr "" -#: bpython/curtsiesfrontend/repl.py:991 +#: bpython/curtsiesfrontend/repl.py:1008 msgid "Session not reevaluated because it was not edited" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1006 +#: bpython/curtsiesfrontend/repl.py:1023 msgid "Session not reevaluated because saved file was blank" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1016 +#: bpython/curtsiesfrontend/repl.py:1033 msgid "Session edited and reevaluated" msgstr "" -#: bpython/curtsiesfrontend/repl.py:1027 +#: bpython/curtsiesfrontend/repl.py:1044 #, python-format msgid "Reloaded at %s by user." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1033 +#: bpython/curtsiesfrontend/repl.py:1050 msgid "Auto-reloading deactivated." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1038 +#: bpython/curtsiesfrontend/repl.py:1055 msgid "Auto-reloading active, watching for file changes..." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1044 +#: bpython/curtsiesfrontend/repl.py:1061 msgid "Auto-reloading not available because watchdog not installed." msgstr "" -#: bpython/curtsiesfrontend/repl.py:1986 +#: bpython/curtsiesfrontend/repl.py:2011 msgid "" "\n" "Thanks for using bpython!\n" From 262f65c319a84ec01d70d0db62f3c8657c813698 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 12 Oct 2021 21:59:11 +0200 Subject: [PATCH 1320/1650] Fix --- doc/sphinx/source/configuration-options.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 6d347761d..4d13cbae5 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -305,7 +305,7 @@ Brings up sincerely cheerful description of bpython features and current key bin .. versionadded:: 0.14 incremental_search -^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^ Default: M-s Perform incremental search on all stored lines in the history. From 0746e559c71009cfc3dafd718caecf640b6258c6 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Sun, 10 Oct 2021 13:09:50 -0700 Subject: [PATCH 1321/1650] remove optional arg of cwcswidth --- bpython/curtsiesfrontend/repl.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 85965f4f6..b25a321ff 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -16,7 +16,6 @@ from typing import Dict, Any, List, Optional, Tuple, Union, cast import blessings -import cwcwidth import greenlet from curtsies import ( FSArray, @@ -283,7 +282,7 @@ def _process_ps(ps, default_ps: str): if not isinstance(ps, str): return ps - return ps if cwcwidth.wcswidth(ps, None) >= 0 else default_ps + return ps if wcswidth(ps) >= 0 else default_ps class BaseRepl(Repl): @@ -1626,8 +1625,8 @@ def move_screen_up(current_line_start_row): ) else: # Common case for determining cursor position cursor_row, cursor_column = divmod( - wcswidth(self.current_cursor_line_without_suggestion.s, None) - - wcswidth(self.current_line, None) + wcswidth(self.current_cursor_line_without_suggestion.s) + - wcswidth(self.current_line) + wcswidth(self.current_line, max(0, self.cursor_offset)) + self.number_of_padding_chars_on_current_cursor_line(), width, From 643a091079f12abacbb34e02490b95c4221414ca Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 15 Oct 2021 22:18:01 +0200 Subject: [PATCH 1322/1650] Return None on error instead of asserting --- bpython/autocomplete.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index fd91ea4af..e9ad61114 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -480,9 +480,11 @@ def matches( r = self.locate(cursor_offset, line) if r is None: return None - curDictParts = lineparts.current_dict(cursor_offset, line) - assert curDictParts, "current_dict when .locate() truthy" - _, _, dexpr = curDictParts + current_dict_parts = lineparts.current_dict(cursor_offset, line) + if current_dict_parts is None: + return None + + _, _, dexpr = current_dict_parts try: obj = safe_eval(dexpr, locals_) except EvaluationError: From 03334d38da1e5ecf6676580c460cbc6d7519711a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 15 Oct 2021 22:22:48 +0200 Subject: [PATCH 1323/1650] Mark match functions as private --- bpython/autocomplete.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index e9ad61114..c422c2a75 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -197,28 +197,28 @@ def few_enough_underscores(current: str, match: str) -> bool: return not match.startswith("_") -def method_match_none(word: str, size: int, text: str) -> bool: +def _method_match_none(word: str, size: int, text: str) -> bool: return False -def method_match_simple(word: str, size: int, text: str) -> bool: +def _method_match_simple(word: str, size: int, text: str) -> bool: return word[:size] == text -def method_match_substring(word: str, size: int, text: str) -> bool: +def _method_match_substring(word: str, size: int, text: str) -> bool: return text in word -def method_match_fuzzy(word: str, size: int, text: str) -> Optional[Match]: +def _method_match_fuzzy(word: str, size: int, text: str) -> Optional[Match]: s = r".*%s.*" % ".*".join(list(text)) return re.search(s, word) -MODES_MAP = { - AutocompleteModes.NONE: method_match_none, - AutocompleteModes.SIMPLE: method_match_simple, - AutocompleteModes.SUBSTRING: method_match_substring, - AutocompleteModes.FUZZY: method_match_fuzzy, +_MODES_MAP = { + AutocompleteModes.NONE: _method_match_none, + AutocompleteModes.SIMPLE: _method_match_simple, + AutocompleteModes.SUBSTRING: _method_match_substring, + AutocompleteModes.FUZZY: _method_match_fuzzy, } @@ -231,7 +231,7 @@ def __init__( mode: AutocompleteModes = AutocompleteModes.SIMPLE, ) -> None: self._shown_before_tab = shown_before_tab - self.method_match = MODES_MAP[mode] + self.method_match = _MODES_MAP[mode] @abc.abstractmethod def matches( From 0cfbd99333b23313d514be76b8c267fdccb94ae0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 15 Oct 2021 22:25:43 +0200 Subject: [PATCH 1324/1650] Make return value and type of _method_match_fuzzy consistent with other match functions --- bpython/autocomplete.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index c422c2a75..f45516c14 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -42,12 +42,9 @@ Dict, Iterator, List, - Match, Optional, Set, - Union, Tuple, - Type, Sequence, ) from . import inspection @@ -209,9 +206,9 @@ def _method_match_substring(word: str, size: int, text: str) -> bool: return text in word -def _method_match_fuzzy(word: str, size: int, text: str) -> Optional[Match]: +def _method_match_fuzzy(word: str, size: int, text: str) -> bool: s = r".*%s.*" % ".*".join(list(text)) - return re.search(s, word) + return re.search(s, word) is not None _MODES_MAP = { From f87fc960a221a00276aa8ab15db79a00c16e755c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 15 Oct 2021 22:34:39 +0200 Subject: [PATCH 1325/1650] Use generator instead of a list --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index f45516c14..911dbb842 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -207,7 +207,7 @@ def _method_match_substring(word: str, size: int, text: str) -> bool: def _method_match_fuzzy(word: str, size: int, text: str) -> bool: - s = r".*%s.*" % ".*".join(list(text)) + s = r".*{}.*".format(".*".join(c for c in text)) return re.search(s, word) is not None From f9b21cafc87f343b861d458c3bc438b0e419c1cc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 15 Oct 2021 22:46:17 +0200 Subject: [PATCH 1326/1650] Also mention #700 --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5f956a1ec..679cfc128 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,13 +18,13 @@ New features: Fixes: +* #700, #884: Fix writing of b"" on fake stdout * #879: Iterate over all completers until a successful one is found * #882: Handle errors in theme configuration without crashing -* #884: Fix writing of b"" on fake stdout * #888: Read PYTHONSTARTUP with utf8 as encoding * #896: Use default sys.ps1 and sys.ps2 if user specified ones are not usable * #902: Do not crash when encountering unreadable files while processing modules for import completion -* #909: Fix sys.stdin.readlin +* #909: Fix sys.stdin.readline * #917: Fix tab completion for dict keys * #919: Replicate python behavior when running with -i and a non-existing file From 126b059ea620ed5c7ef6324bd8c3c0950cc54b3e Mon Sep 17 00:00:00 2001 From: gpotter2 Date: Thu, 21 Oct 2021 14:07:09 +0200 Subject: [PATCH 1327/1650] Fix advanced class completion --- bpython/repl.py | 2 -- bpython/test/test_repl.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index e261e61ad..5d664e659 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -601,8 +601,6 @@ def get_args(self): if inspect.isclass(f): class_f = None - if hasattr(f, "__init__") and f.__init__ is not object.__init__: - class_f = f.__init__ if ( (not class_f or not inspection.getfuncprops(func, class_f)) and hasattr(f, "__new__") diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 65a2fb813..e29c5a4e5 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -482,6 +482,34 @@ def test_paremeter_name_completion(self): self.repl.matches_iter.matches, ["abc=", "abd=", "abs("] ) + def test_parameter_advanced_on_class(self): + self.repl = FakeRepl( + {"autocomplete_mode": autocomplete.AutocompleteModes.SIMPLE} + ) + self.set_input_line("TestCls(app") + + code = """ + import inspect + + class TestCls: + # A class with boring __init__ typing + def __init__(self, *args, **kwargs): + pass + # But that uses super exotic typings recognized by inspect.signature + __signature__ = inspect.Signature([ + inspect.Parameter("apple", inspect.Parameter.POSITIONAL_ONLY), + inspect.Parameter("apple2", inspect.Parameter.KEYWORD_ONLY), + inspect.Parameter("pinetree", inspect.Parameter.KEYWORD_ONLY), + ]) + """ + for line in code.split("\n"): + print(line[8:]) + self.repl.push(line[8:]) + + self.assertTrue(self.repl.complete()) + self.assertTrue(hasattr(self.repl.matches_iter, "matches")) + self.assertEqual(self.repl.matches_iter.matches, ["apple2=", "apple="]) + class TestCliRepl(unittest.TestCase): def setUp(self): From bb6eda80796cc9d8197f5c5514b9e2c2c54aea84 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 22 Oct 2021 12:27:42 +0200 Subject: [PATCH 1328/1650] Mention #970 in the changelog [skip ci] --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 679cfc128..256e5d855 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,6 +27,8 @@ Fixes: * #909: Fix sys.stdin.readline * #917: Fix tab completion for dict keys * #919: Replicate python behavior when running with -i and a non-existing file +* #932: Fix handling of __signature__ for completion. + Thanks to gpotter2 Changes to dependencies: From f51d61bb4eb25cc0eb74302de90b5c7fefc21803 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 30 Oct 2021 16:04:13 +0200 Subject: [PATCH 1329/1650] Improve signature of __exit__ --- bpython/curtsiesfrontend/_internal.py | 11 ++++++++++- bpython/curtsiesfrontend/repl.py | 12 +++++++++--- bpython/filelock.py | 5 +++-- bpython/inspection.py | 12 +++++++++--- bpython/repl.py | 12 +++++++++--- 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index f918f6fc5..bc8e02c70 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -21,6 +21,9 @@ # THE SOFTWARE. import pydoc +from types import TracebackType +from typing import Optional, Literal, Type + from .. import _internal @@ -29,8 +32,14 @@ def __enter__(self): self._orig_pager = pydoc.pager pydoc.pager = self - def __exit__(self, *args): + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Literal[False]: pydoc.pager = self._orig_pager + return False def __call__(self, text): return None diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index b25a321ff..a9f911e3f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -12,8 +12,8 @@ import time import unicodedata from enum import Enum - -from typing import Dict, Any, List, Optional, Tuple, Union, cast +from types import TracebackType +from typing import Dict, Any, List, Optional, Tuple, Union, cast, Literal, Type import blessings import greenlet @@ -573,7 +573,12 @@ def __enter__(self): sitefix.monkeypatch_quit() return self - def __exit__(self, *args): + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Literal[False]: sys.stdin = self.orig_stdin sys.stdout = self.orig_stdout sys.stderr = self.orig_stderr @@ -584,6 +589,7 @@ def __exit__(self, *args): signal.signal(signal.SIGTSTP, self.orig_sigtstp_handler) sys.meta_path = self.orig_meta_path + return False def sigwinch_handler(self, signum, frame): old_rows, old_columns = self.height, self.width diff --git a/bpython/filelock.py b/bpython/filelock.py index 3d77c439c..11f575b6e 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from typing import Optional, Type, IO +from typing import Optional, Type, IO, Literal from types import TracebackType has_fcntl = True @@ -59,9 +59,10 @@ def __exit__( exc_type: Optional[Type[BaseException]], exc: Optional[BaseException], exc_tb: Optional[TracebackType], - ) -> None: + ) -> Literal[False]: if self.locked: self.release() + return False def __del__(self) -> None: if self.locked: diff --git a/bpython/inspection.py b/bpython/inspection.py index 7f95fbd20..e3f8aba26 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -26,11 +26,11 @@ import pydoc import re from collections import namedtuple +from typing import Any, Optional, Literal, Type +from types import MemberDescriptorType, TracebackType from pygments.token import Token from pygments.lexers import Python3Lexer -from typing import Any -from types import MemberDescriptorType from .lazyre import LazyReCompile @@ -88,7 +88,12 @@ def __enter__(self): self.attribs = (__getattribute__, __getattr__) # /Dark magic - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Literal[False]: """Restore an object's magic methods.""" type_ = type(self.obj) __getattribute__, __getattr__ = self.attribs @@ -98,6 +103,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): if __getattr__ is not None: setattr(type_, "__getattr__", __getattr__) # /Dark magic + return False class _Repr: diff --git a/bpython/repl.py b/bpython/repl.py index 5d664e659..a21593e9c 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -36,10 +36,11 @@ from abc import abstractmethod from itertools import takewhile from pathlib import Path +from types import ModuleType, TracebackType +from typing import cast, Tuple, Any, Optional, Literal, Type + from pygments.lexers import Python3Lexer from pygments.token import Token -from types import ModuleType -from typing import cast, Tuple, Any have_pyperclip = True try: @@ -68,7 +69,12 @@ def __init__(self): def __enter__(self): self.start = self.time() - def __exit__(self, ty, val, tb): + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Literal[False]: self.last_command = self.time() - self.start self.running_time += self.last_command return False From 194a00094f20f5747bc25fac564c0931b997ba6c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 31 Oct 2021 15:42:06 +0100 Subject: [PATCH 1330/1650] Fix import of Literal --- bpython/curtsiesfrontend/_internal.py | 3 ++- bpython/curtsiesfrontend/repl.py | 3 ++- bpython/filelock.py | 3 ++- bpython/inspection.py | 3 ++- bpython/repl.py | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index bc8e02c70..633174a88 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -22,7 +22,8 @@ import pydoc from types import TracebackType -from typing import Optional, Literal, Type +from typing import Optional, Type +from typing_extensions import Literal from .. import _internal diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index a9f911e3f..6007f6157 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -13,7 +13,8 @@ import unicodedata from enum import Enum from types import TracebackType -from typing import Dict, Any, List, Optional, Tuple, Union, cast, Literal, Type +from typing import Dict, Any, List, Optional, Tuple, Union, cast, Type +from typing_extensions import Literal import blessings import greenlet diff --git a/bpython/filelock.py b/bpython/filelock.py index 11f575b6e..6558fc583 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -20,7 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from typing import Optional, Type, IO, Literal +from typing import Optional, Type, IO +from typing_extensions import Literal from types import TracebackType has_fcntl = True diff --git a/bpython/inspection.py b/bpython/inspection.py index e3f8aba26..08ad47c95 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -26,8 +26,9 @@ import pydoc import re from collections import namedtuple -from typing import Any, Optional, Literal, Type +from typing import Any, Optional, Type from types import MemberDescriptorType, TracebackType +from typing_extensions import Literal from pygments.token import Token from pygments.lexers import Python3Lexer diff --git a/bpython/repl.py b/bpython/repl.py index a21593e9c..32d08dbf2 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -37,7 +37,8 @@ from itertools import takewhile from pathlib import Path from types import ModuleType, TracebackType -from typing import cast, Tuple, Any, Optional, Literal, Type +from typing import cast, Tuple, Any, Optional, Type +from typing_extensions import Literal from pygments.lexers import Python3Lexer from pygments.token import Token From 29fc2a221c69d33f3366e36124df41f3c041af75 Mon Sep 17 00:00:00 2001 From: supakeen Date: Sun, 7 Nov 2021 19:30:52 +0100 Subject: [PATCH 1331/1650] The contents can't be rendered by PyPi and block release. --- README.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.rst b/README.rst index 539d036bd..976314c55 100644 --- a/README.rst +++ b/README.rst @@ -34,11 +34,6 @@ etc. You can find more about bpython - including `full documentation`_ - at our `homepage`_. -.. contents:: - :local: - :depth: 1 - :backlinks: none - ========================== Installation & Basic Usage ========================== From 2ba5de04ef8675ae5287cb0bcfa19741582667f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 15 Oct 2021 23:28:41 +0200 Subject: [PATCH 1332/1650] Add some return types --- bpython/inspection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 08ad47c95..e7f705147 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -267,7 +267,7 @@ def getfuncprops(func, f): return fprops -def is_eval_safe_name(string): +def is_eval_safe_name(string: str) -> bool: return all( part.isidentifier() and not keyword.iskeyword(part) for part in string.split(".") @@ -334,7 +334,7 @@ def get_argspec_from_signature(f): get_encoding_line_re = LazyReCompile(r"^.*coding[:=]\s*([-\w.]+).*$") -def get_encoding(obj): +def get_encoding(obj) -> str: """Try to obtain encoding information of the source of an object.""" for line in inspect.findsource(obj)[0][:2]: m = get_encoding_line_re.search(line) @@ -354,7 +354,7 @@ def get_encoding_file(fname: str) -> str: return "utf8" -def getattr_safe(obj: Any, name: str): +def getattr_safe(obj: Any, name: str) -> Any: """side effect free getattr (calls getattr_static).""" result = inspect.getattr_static(obj, name) # Slots are a MemberDescriptorType @@ -371,6 +371,6 @@ def hasattr_safe(obj: Any, name: str) -> bool: return False -def get_source_unicode(obj): +def get_source_unicode(obj) -> str: """Returns a decoded source of object""" return inspect.getsource(obj) From 87f305c023641ea6fbc7aed0eaa0cf3ea87f3965 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Nov 2021 21:01:04 +0100 Subject: [PATCH 1333/1650] Bump required Python version to >=3.7 By the time bpython 0.23 is released, Python 3.6 will be EOL. --- .github/workflows/build.yaml | 2 +- doc/sphinx/source/contributing.rst | 2 +- doc/sphinx/source/releases.rst | 2 +- pyproject.toml | 2 +- requirements.txt | 1 - setup.cfg | 3 +-- 6 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index dc1fc9ed2..05e3f65d4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,7 +13,7 @@ jobs: continue-on-error: ${{ matrix.python-version == 'pypy3' }} strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3] + python-version: [3.7, 3.8, 3.9, "3.10", pypy3] steps: - uses: actions/checkout@v2 with: diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 76d3b9402..54fd56c6b 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -17,7 +17,7 @@ the time of day. Getting your development environment set up ------------------------------------------- -bpython supports Python 3.6 and newer. The code is compatible with all +bpython supports Python 3.7 and newer. The code is compatible with all supported versions. Using a virtual environment is probably a good idea. Create a virtual diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst index 1f723f592..738c24ff2 100644 --- a/doc/sphinx/source/releases.rst +++ b/doc/sphinx/source/releases.rst @@ -45,7 +45,7 @@ A checklist to perform some manual tests before a release: Check that all of the following work before a release: -* Runs under Python 3.6 - 3.9 +* Runs under Python 3.7 - 3.9 * Save * Rewind * Pastebin diff --git a/pyproject.toml b/pyproject.toml index 79a558e88..c7ef64f28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = [ [tool.black] line-length = 80 -target_version = ["py36"] +target_version = ["py37"] include = '\.pyi?$' exclude = ''' /( diff --git a/requirements.txt b/requirements.txt index a989bdf40..7f56dc0fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ Pygments backports.cached-property; python_version < "3.8" curtsies >=0.3.5 cwcwidth -dataclasses; python_version < "3.7" greenlet pyxdg requests diff --git a/setup.cfg b/setup.cfg index 1efed779c..806c01c89 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,7 @@ classifiers = Programming Language :: Python :: 3 [options] -python_requires = >=3.6 +python_requires = >=3.7 packages = bpython bpython.curtsiesfrontend @@ -22,7 +22,6 @@ packages = install_requires = backports.cached-property; python_version < "3.8" curtsies >=0.3.5 - dataclasses; python_version < "3.7" cwcwidth greenlet pygments From f8b5962027719beb761b20f1b46ee839072c2060 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Nov 2021 21:22:47 +0100 Subject: [PATCH 1334/1650] Remove get_source_unicode It's just a call to inspect.getsource anyway. --- bpython/inspection.py | 5 ----- bpython/repl.py | 2 +- bpython/test/test_inspection.py | 7 ++++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index e7f705147..e7ab0a155 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -369,8 +369,3 @@ def hasattr_safe(obj: Any, name: str) -> bool: return True except AttributeError: return False - - -def get_source_unicode(obj) -> str: - """Returns a decoded source of object""" - return inspect.getsource(obj) diff --git a/bpython/repl.py b/bpython/repl.py index 32d08dbf2..9ffeba328 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -649,7 +649,7 @@ def get_source_of_current_name(self): raise SourceNotFound(_("Nothing to get source of")) if inspection.is_eval_safe_name(line): obj = self.get_object(line) - return inspection.get_source_unicode(obj) + return inspect.getsource(obj) except (AttributeError, NameError) as e: msg = _("Cannot get source: %s") % (e,) except OSError as e: diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 50be0c3a2..550f8731d 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -1,3 +1,4 @@ +import inspect import os import sys import unittest @@ -90,17 +91,17 @@ def test_get_encoding_utf8(self): def test_get_source_ascii(self): self.assertEqual( - inspection.get_source_unicode(encoding_ascii.foo), foo_ascii_only + inspect.getsource(encoding_ascii.foo), foo_ascii_only ) def test_get_source_utf8(self): self.assertEqual( - inspection.get_source_unicode(encoding_utf8.foo), foo_non_ascii + inspect.getsource(encoding_utf8.foo), foo_non_ascii ) def test_get_source_latin1(self): self.assertEqual( - inspection.get_source_unicode(encoding_latin1.foo), foo_non_ascii + inspect.getsource(encoding_latin1.foo), foo_non_ascii ) def test_get_source_file(self): From 72ee717a04368c48e3233d545e5cf517fbaf6a0c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Nov 2021 21:42:53 +0100 Subject: [PATCH 1335/1650] Turn safe_glob into a function --- bpython/autocomplete.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 911dbb842..29a02141a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -343,13 +343,14 @@ def format(self, word: str) -> str: return after_last_dot(word) +def _safe_glob(pathname: str) -> Iterator[str]: + return glob.iglob(glob.escape(pathname) + "*") + + class FilenameCompletion(BaseCompletionType): def __init__(self, mode: AutocompleteModes = AutocompleteModes.SIMPLE): super().__init__(False, mode) - def safe_glob(self, pathname: str) -> Iterator[str]: - return glob.iglob(glob.escape(pathname) + "*") - def matches( self, cursor_offset: int, line: str, **kwargs: Any ) -> Optional[Set]: @@ -359,7 +360,7 @@ def matches( matches = set() username = cs.word.split(os.path.sep, 1)[0] user_dir = os.path.expanduser(username) - for filename in self.safe_glob(os.path.expanduser(cs.word)): + for filename in _safe_glob(os.path.expanduser(cs.word)): if os.path.isdir(filename): filename += os.path.sep if cs.word.startswith("~"): From 88de05a8fdfc16cadf4cb286d0a9090b128c1647 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Nov 2021 21:43:07 +0100 Subject: [PATCH 1336/1650] Remove useless if --- bpython/autocomplete.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 29a02141a..40d655b03 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -571,15 +571,15 @@ def matches( r = self.locate(cursor_offset, line) if r is None: return None - if argspec: - matches = { - f"{name}=" - for name in argspec[1][0] - if isinstance(name, str) and name.startswith(r.word) - } - matches.update( - name + "=" for name in argspec[1][4] if name.startswith(r.word) - ) + + matches = { + f"{name}=" + for name in argspec[1][0] + if isinstance(name, str) and name.startswith(r.word) + } + matches.update( + name + "=" for name in argspec[1][4] if name.startswith(r.word) + ) return matches if matches else None def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: From 535d4e9452bd0ebdcc5d715b3d825f60208f143a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Nov 2021 21:53:50 +0100 Subject: [PATCH 1337/1650] Apply black --- bpython/test/test_inspection.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 550f8731d..fdbb959c9 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -90,19 +90,13 @@ def test_get_encoding_utf8(self): self.assertEqual(inspection.get_encoding(encoding_utf8.foo), "utf-8") def test_get_source_ascii(self): - self.assertEqual( - inspect.getsource(encoding_ascii.foo), foo_ascii_only - ) + self.assertEqual(inspect.getsource(encoding_ascii.foo), foo_ascii_only) def test_get_source_utf8(self): - self.assertEqual( - inspect.getsource(encoding_utf8.foo), foo_non_ascii - ) + self.assertEqual(inspect.getsource(encoding_utf8.foo), foo_non_ascii) def test_get_source_latin1(self): - self.assertEqual( - inspect.getsource(encoding_latin1.foo), foo_non_ascii - ) + self.assertEqual(inspect.getsource(encoding_latin1.foo), foo_non_ascii) def test_get_source_file(self): path = os.path.join( From 2f7899bb9bf28fd820df0707fd2722830254bbc5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Nov 2021 22:07:50 +0100 Subject: [PATCH 1338/1650] Use more f-strings --- bpython/curtsiesfrontend/interpreter.py | 2 +- bpython/curtsiesfrontend/repl.py | 2 +- bpython/formatter.py | 4 ++-- bpython/simpleeval.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 7f7c2fcc7..48ee15bae 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -55,7 +55,7 @@ def format(self, tokensource, outfile): for token, text in tokensource: while token not in self.f_strings: token = token.parent - o += "{}\x03{}\x04".format(self.f_strings[token], text) + o += f"{self.f_strings[token]}\x03{text}\x04" outfile.write(parse(o.rstrip())) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 6007f6157..54b707806 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -2067,7 +2067,7 @@ def key_help_text(self): max_func = max(len(func) for func, key in pairs) return "\n".join( - "{} : {}".format(func.rjust(max_func), key) for func, key in pairs + f"{func.rjust(max_func)} : {key}" for func, key in pairs ) def get_session_formatted_for_file(self) -> str: diff --git a/bpython/formatter.py b/bpython/formatter.py index 9fdd43a57..9618979aa 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -99,7 +99,7 @@ class BPythonFormatter(Formatter): def __init__(self, color_scheme, **options): self.f_strings = {} for k, v in theme_map.items(): - self.f_strings[k] = "\x01{}".format(color_scheme[v]) + self.f_strings[k] = f"\x01{color_scheme[v]}" if k is Parenthesis: # FIXME: Find a way to make this the inverse of the current # background colour @@ -114,7 +114,7 @@ def format(self, tokensource, outfile): while token not in self.f_strings: token = token.parent - o += "{}\x03{}\x04".format(self.f_strings[token], text) + o += f"{self.f_strings[token]}\x03{text}\x04" outfile.write(o.rstrip()) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index c5e14cf5d..3992a70fc 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -203,7 +203,7 @@ def safe_getitem(obj, index): return obj[index] except (KeyError, IndexError): raise EvaluationError(f"can't lookup key {index!r} on {obj!r}") - raise ValueError("unsafe to lookup on object of type {}".format(type(obj))) + raise ValueError(f"unsafe to lookup on object of type {type(obj)}") def find_attribute_with_name(node, name): From 5c6aa63a39d5666511dd4b693a0107809894cc7f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Nov 2021 22:11:13 +0100 Subject: [PATCH 1339/1650] Remove useless statement --- bpython/autocomplete.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 40d655b03..7a65d1b30 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -372,7 +372,6 @@ def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: return lineparts.current_string(cursor_offset, line) def format(self, filename: str) -> str: - filename.rstrip(os.sep).rsplit(os.sep)[-1] if os.sep in filename[:-1]: return filename[filename.rindex(os.sep, 0, -1) + 1 :] else: From 143e4e55d8f5227149528a5880a32a516a40f14d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 7 Nov 2021 22:12:34 +0100 Subject: [PATCH 1340/1650] Start development of 0.23 --- CHANGELOG.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 256e5d855..062a711b4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,17 @@ Changelog ========= +0.23 +---- + +General information: + +New features: + +Fixes: + +Support for Python 3.6 has been dropped. + 0.22 ---- From 47c75308298be8099c8f0c4db4d67c2b1bd7c7ec Mon Sep 17 00:00:00 2001 From: Dustin Rodrigues Date: Sun, 7 Nov 2021 23:42:17 -0500 Subject: [PATCH 1341/1650] add typing-extensions requirement --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 806c01c89..96ef47be1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,7 @@ install_requires = pygments pyxdg requests + typing-extensions [options.extras_require] clipboard = pyperclip From 532dac009bc1c7f64012c7c66c75ffdd4da49275 Mon Sep 17 00:00:00 2001 From: Dustin Rodrigues Date: Sun, 7 Nov 2021 23:42:17 -0500 Subject: [PATCH 1342/1650] add typing-extensions requirement --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 1efed779c..001fa1334 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ install_requires = pygments pyxdg requests + typing-extensions [options.extras_require] clipboard = pyperclip From 429749f8ee90af60455af099dd0871975ccace71 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 8 Nov 2021 09:54:27 +0100 Subject: [PATCH 1343/1650] Remove unused import --- bpython/curtsies.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 86d33cf3f..1d41c3b5c 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -24,16 +24,16 @@ from typing import ( Any, + Callable, Dict, + Generator, List, - Callable, - Union, + Optional, Sequence, Tuple, - Optional, - Generator, + Union, ) -from typing_extensions import Literal, Protocol +from typing_extensions import Protocol logger = logging.getLogger(__name__) From 2d6a0b6125494d3be49cf021da7926dae71afe90 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 8 Nov 2021 10:32:35 +0100 Subject: [PATCH 1344/1650] Add changelog entry for 0.22.1 --- CHANGELOG.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 256e5d855..00244b1c3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========= +0.22.1 +------ + +Fixes: + +* #938: Fix missing dependency on typing_extensions. + Thanks to Dustin Rodrigues + 0.22 ---- From 8411460a9cdc75166ca1935e3acb9646c4218109 Mon Sep 17 00:00:00 2001 From: supakeen Date: Mon, 8 Nov 2021 14:02:48 +0100 Subject: [PATCH 1345/1650] Re-order installation, usage, and OS installation. - Removal of the Windows subsection (these will move to docs). - Rename of "Mac OS" to "macOS". - Remove 'curses' from the title. - Remove 'alternatives' section. --- README.rst | 165 +++++++++++++++++------------------------------------ 1 file changed, 52 insertions(+), 113 deletions(-) diff --git a/README.rst b/README.rst index 976314c55..9eedbd45c 100644 --- a/README.rst +++ b/README.rst @@ -8,9 +8,9 @@ :target: https://github.com/ambv/black -*********************************************************************** -bpython: A fancy curses interface to the Python interactive interpreter -*********************************************************************** +**************************************************************** +bpython: A fancy interface to the Python interactive interpreter +**************************************************************** `bpython`_ is a lightweight Python interpreter that adds several features common to IDEs. These features include **syntax highlighting**, **expected parameter @@ -51,71 +51,6 @@ Start bpython by typing ``bpython`` in your terminal. You can exit bpython by using the ``exit()`` command or by pressing control-D like regular interactive Python. -Installation via OS Package Manager ------------------------------------ - -The majority of desktop computer operating systems come with package management -systems. If you use one of these OSes, you can install ``bpython`` using the -package manager. - -Ubuntu/Debian -~~~~~~~~~~~~~ -Ubuntu/Debian family Linux users can install ``bpython`` using the ``apt`` -package manager, using the command with ``sudo`` privileges: - -.. code-block:: bash - - $ apt install bpython - -In case you are using an older version, run - -.. code-block:: bash - - $ apt-get install bpython - -Arch Linux -~~~~~~~~~~ -Arch Linux uses ``pacman`` as the default package manager; you can use it to install ``bpython``: - -.. code-block:: bash - - $ pacman -S bpython - -Fedora -~~~~~~~~~~ -Fedora users can install ``bpython`` directly from the command line using ``dnf``. - -.. code-block:: bash - - $ dnf install bpython - -Windows -~~~~~~~ -**Caveats:** As ``bpython`` makes use of the ncurses library of \*nix-family operating systems, -bpython on Windows is not officially supported and tested. - -However, you may still use bpython on Windows using a workaround. In brief, you should install -these two packages using ``pip``: - -.. code-block:: bash - - $ pip install bpython windows-curses - -Then you should invoke a program called ``bpython-curses.exe`` instead of ``bpython.exe`` to use bpython: - -.. code-block:: bash - - $ bpython-curses - -Mac OS -~~~~~~ -Like Windows, Mac OS does not include a package manager by default. If you have installed any -third-party package manager like MacPorts, you can install it via - -.. code-block:: bash - - $ sudo port install py-bpython - =================== Features & Examples =================== @@ -172,69 +107,73 @@ bpython-urwid * urwid -========== -Known Bugs -========== -For known bugs please see bpython's `known issues and FAQ`_ page. -====================== -Contact & Contributing -====================== -I hope you find it useful and please feel free to submit any bugs/patches -suggestions to `Robert`_ or place them on the GitHub -`issues tracker`_. +=================================== +Installation via OS Package Manager +=================================== -For any other ways of communicating with bpython users and devs you can find us -at the community page on the `project homepage`_, or in the `community`_. +The majority of desktop computer operating systems come with package management +systems. If you use one of these OSes, you can install ``bpython`` using the +package manager. -Hope to see you there! +Ubuntu/Debian +~~~~~~~~~~~~~ +Ubuntu/Debian family Linux users can install ``bpython`` using the ``apt`` +package manager, using the command with ``sudo`` privileges: -=================== -CLI Windows Support -=================== +.. code-block:: bash -Dependencies ------------- -`Curses`_ Use the appropriate version compiled by Christoph Gohlke. + $ apt install bpython -`pyreadline`_ Use the version in the cheeseshop. +In case you are using an older version, run -Recommended ------------ -Obtain the less program from GnuUtils. This makes the pager work as intended. -It can be obtained from cygwin or GnuWin32 or msys +.. code-block:: bash -Current version is tested with ------------------------------- -* Curses 2.2 -* pyreadline 1.7 + $ apt-get install bpython -Curses Notes ------------- -The curses used has a bug where the colours are displayed incorrectly: +Arch Linux +~~~~~~~~~~ +Arch Linux uses ``pacman`` as the default package manager; you can use it to install ``bpython``: -* red is swapped with blue -* cyan is swapped with yellow +.. code-block:: bash -To correct this I have provided a windows.theme file. + $ pacman -S bpython -This curses implementation has 16 colors (dark and light versions of the -colours) +Fedora +~~~~~~~~~~ +Fedora users can install ``bpython`` directly from the command line using ``dnf``. +.. code-block:: bash + + $ dnf install bpython + +macOS +~~~~~ +macOS does not include a package manager by default. If you have installed any +third-party package manager like MacPorts, you can install it via + +.. code-block:: bash + + $ sudo port install py-bpython -============ -Alternatives -============ -`ptpython`_ +========== +Known Bugs +========== +For known bugs please see bpython's `known issues and FAQ`_ page. + +====================== +Contact & Contributing +====================== +I hope you find it useful and please feel free to submit any bugs/patches +suggestions to `Robert`_ or place them on the GitHub +`issues tracker`_. -`IPython`_ +For any other ways of communicating with bpython users and devs you can find us +at the community page on the `project homepage`_, or in the `community`_. -Feel free to get in touch if you know of any other alternatives that people -may be interested to try. +Hope to see you there! -.. _ptpython: https://github.com/jonathanslenders/ptpython -.. _ipython: https://ipython.org/ .. _homepage: http://www.bpython-interpreter.org .. _full documentation: http://docs.bpython-interpreter.org/ .. _issues tracker: http://github.com/bpython/bpython/issues/ From 58da471da995637e586b5274cad61470617dbf52 Mon Sep 17 00:00:00 2001 From: supakeen Date: Mon, 8 Nov 2021 14:16:15 +0100 Subject: [PATCH 1346/1650] Change title levels to match tree. --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 9eedbd45c..6cfd5663e 100644 --- a/README.rst +++ b/README.rst @@ -117,7 +117,7 @@ systems. If you use one of these OSes, you can install ``bpython`` using the package manager. Ubuntu/Debian -~~~~~~~~~~~~~ +------------- Ubuntu/Debian family Linux users can install ``bpython`` using the ``apt`` package manager, using the command with ``sudo`` privileges: @@ -132,7 +132,7 @@ In case you are using an older version, run $ apt-get install bpython Arch Linux -~~~~~~~~~~ +---------- Arch Linux uses ``pacman`` as the default package manager; you can use it to install ``bpython``: .. code-block:: bash @@ -140,7 +140,7 @@ Arch Linux uses ``pacman`` as the default package manager; you can use it to ins $ pacman -S bpython Fedora -~~~~~~~~~~ +------ Fedora users can install ``bpython`` directly from the command line using ``dnf``. .. code-block:: bash @@ -148,7 +148,7 @@ Fedora users can install ``bpython`` directly from the command line using ``dnf` $ dnf install bpython macOS -~~~~~ +----- macOS does not include a package manager by default. If you have installed any third-party package manager like MacPorts, you can install it via From b4578ba50bc12cc0e28a7fc0f996e30cf66ca366 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 8 Nov 2021 21:11:14 +0100 Subject: [PATCH 1347/1650] Remove unused next method next was used in Python 2.x. --- bpython/repl.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 9ffeba328..e1a5429d5 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -265,9 +265,6 @@ def current(self): raise ValueError("No current match.") return self.matches[self.index] - def next(self): - return self.__next__() - def __next__(self): self.index = (self.index + 1) % len(self.matches) return self.matches[self.index] From 3ba6e169f4b6531d6d99619c978eb6174140d336 Mon Sep 17 00:00:00 2001 From: samuelgregorovic <47627184+samuelgregorovic@users.noreply.github.com> Date: Tue, 9 Nov 2021 14:50:21 +0100 Subject: [PATCH 1348/1650] Brackets auto-close (#934) Auto-close for charcter pairs such as (), [], "", and ''. Not enabled by default, can be enabled in the bpython config file. --- bpython/config.py | 4 + bpython/curtsiesfrontend/manual_readline.py | 15 +- bpython/curtsiesfrontend/repl.py | 109 +++++++++++++- bpython/line.py | 23 +++ bpython/sample-config | 2 + bpython/test/test_brackets_completion.py | 149 ++++++++++++++++++++ 6 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 bpython/test/test_brackets_completion.py diff --git a/bpython/config.py b/bpython/config.py index 0127b0cbc..c6cadf85d 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -156,6 +156,7 @@ class Config: "syntax": True, "tab_length": 4, "unicode_box": True, + "brackets_completion": False, }, "keyboard": { "backspace": "C-h", @@ -362,6 +363,9 @@ def get_key_no_doublebind(command: str) -> str: if self.unicode_box and supports_box_chars() else ("|", "|", "-", "-", "+", "+", "+", "+") ) + self.brackets_completion = config.getboolean( + "general", "brackets_completion" + ) def load_theme( diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index afeb55531..f95e66c59 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -5,9 +5,10 @@ based on http://www.bigsmoke.us/readline/shortcuts""" from ..lazyre import LazyReCompile - import inspect +from ..line import cursor_on_closing_char_pair + INDENT = 4 # TODO Allow user config of keybindings for these actions @@ -244,6 +245,18 @@ def backspace(cursor_offset, line): cursor_offset - to_delete, line[: cursor_offset - to_delete] + line[cursor_offset:], ) + # removes opening bracket along with closing bracket + # if there is nothing between them + # TODO: could not get config value here, works even without -B option + on_closing_char, pair_close = cursor_on_closing_char_pair( + cursor_offset, line + ) + if on_closing_char and pair_close: + return ( + cursor_offset - 1, + line[: cursor_offset - 1] + line[cursor_offset + 1 :], + ) + return (cursor_offset - 1, line[: cursor_offset - 1] + line[cursor_offset:]) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 54b707806..6693c8517 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -46,7 +46,7 @@ Interp, code_finished_will_parse, ) -from .manual_readline import edit_keys +from .manual_readline import edit_keys, cursor_on_closing_char_pair from .parse import parse as bpythonparse, func_for_letter, color_for_letter from .preprocess import preprocess from .. import __version__ @@ -58,6 +58,7 @@ SourceNotFound, ) from ..translations import _ +from ..line import CHARACTER_PAIR_MAP logger = logging.getLogger(__name__) @@ -800,9 +801,74 @@ def process_key_event(self, e: str) -> None: self.incr_search_mode = None elif e in ("",): self.add_normal_character(" ") + elif e in CHARACTER_PAIR_MAP.keys(): + if e in ["'", '"']: + if self.is_closing_quote(e): + self.insert_char_pair_end(e) + else: + self.insert_char_pair_start(e) + else: + self.insert_char_pair_start(e) + elif e in CHARACTER_PAIR_MAP.values(): + self.insert_char_pair_end(e) else: self.add_normal_character(e) + def is_closing_quote(self, e): + char_count = self._current_line.count(e) + if ( + char_count % 2 == 0 + and cursor_on_closing_char_pair( + self._cursor_offset, self._current_line, e + )[0] + ): + return True + return False + + def insert_char_pair_start(self, e): + """Accepts character which is a part of CHARACTER_PAIR_MAP + like brackets and quotes, and appends it to the line with + an appropriate character pair ending. Closing character can only be inserted + when the next character is either a closing character or a space + + e.x. if you type "(" (lparen) , this will insert "()" + into the line + """ + self.add_normal_character(e) + if self.config.brackets_completion: + allowed_chars = ["}", ")", "]", " "] + start_of_line = len(self._current_line) == 1 + end_of_line = len(self._current_line) == self._cursor_offset + can_lookup_next = len(self._current_line) > self._cursor_offset + next_char = ( + None + if not can_lookup_next + else self._current_line[self._cursor_offset] + ) + next_char_allowed = next_char in allowed_chars + if start_of_line or end_of_line or next_char_allowed: + closing_char = CHARACTER_PAIR_MAP[e] + self.add_normal_character(closing_char, narrow_search=False) + self._cursor_offset -= 1 + + def insert_char_pair_end(self, e): + """Accepts character which is a part of CHARACTER_PAIR_MAP + like brackets and quotes, and checks whether it should be + inserted to the line or overwritten + + e.x. if you type ")" (rparen) , and your cursor is directly + above another ")" (rparen) in the cmd, this will just skip + it and move the cursor. + If there is no same character underneath the cursor, the + character will be printed/appended to the line + """ + if self.config.brackets_completion: + if self.cursor_offset < len(self._current_line): + if self._current_line[self.cursor_offset] == e: + self.cursor_offset += 1 + return + self.add_normal_character(e) + def get_last_word(self): previous_word = _last_word(self.rl_history.entry) @@ -903,7 +969,15 @@ def only_whitespace_left_of_cursor(): for unused in range(to_add): self.add_normal_character(" ") return - + # if cursor on closing character from pair, + # moves cursor behind it on tab + # ? should we leave it here as default? + if self.config.brackets_completion: + on_closing_char, _ = cursor_on_closing_char_pair( + self._cursor_offset, self._current_line + ) + if on_closing_char: + self._cursor_offset += 1 # run complete() if we don't already have matches if len(self.matches_iter.matches) == 0: self.list_win_visible = self.complete(tab=True) @@ -915,7 +989,6 @@ def only_whitespace_left_of_cursor(): # using _current_line so we don't trigger a completion reset if not self.matches_iter.matches: self.list_win_visible = self.complete() - elif self.matches_iter.matches: self.current_match = ( back and self.matches_iter.previous() or next(self.matches_iter) @@ -924,6 +997,24 @@ def only_whitespace_left_of_cursor(): self._cursor_offset, self._current_line = cursor_and_line # using _current_line so we don't trigger a completion reset self.list_win_visible = True + if self.config.brackets_completion: + # appends closing char pair if completion is a callable + if self.is_completion_callable(self._current_line): + self._current_line = self.append_closing_character( + self._current_line + ) + + def is_completion_callable(self, completion): + """Checks whether given completion is callable (e.x. function)""" + completion_end = completion[-1] + return completion_end in CHARACTER_PAIR_MAP + + def append_closing_character(self, completion): + """Appends closing character/bracket to the completion""" + completion_end = completion[-1] + if completion_end in CHARACTER_PAIR_MAP: + completion = f"{completion}{CHARACTER_PAIR_MAP[completion_end]}" + return completion def on_control_d(self): if self.current_line == "": @@ -1071,7 +1162,7 @@ def toggle_file_watch(self): ) # Handler Helpers - def add_normal_character(self, char): + def add_normal_character(self, char, narrow_search=True): if len(char) > 1 or is_nop(char): return if self.incr_search_mode: @@ -1087,12 +1178,18 @@ def add_normal_character(self, char): reset_rl_history=False, clear_special_mode=False, ) - self.cursor_offset += 1 + if narrow_search: + self.cursor_offset += 1 + else: + self._cursor_offset += 1 if self.config.cli_trim_prompts and self.current_line.startswith( self.ps1 ): self.current_line = self.current_line[4:] - self.cursor_offset = max(0, self.cursor_offset - 4) + if narrow_search: + self.cursor_offset = max(0, self.cursor_offset - 4) + else: + self._cursor_offset += max(0, self.cursor_offset - 4) def add_to_incremental_search(self, char=None, backspace=False): """Modify the current search term while in incremental search. diff --git a/bpython/line.py b/bpython/line.py index b98302dd2..cbfa682de 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -19,6 +19,7 @@ class LinePart(NamedTuple): _current_word_re = LazyReCompile(r"(? Optional[LinePart]: @@ -287,3 +288,25 @@ def current_expression_attribute( if m.start(1) <= cursor_offset <= m.end(1): return LinePart(m.start(1), m.end(1), m.group(1)) return None + + +def cursor_on_closing_char_pair(cursor_offset, line, ch=None): + """Checks if cursor sits on closing character of a pair + and whether its pair character is directly behind it + """ + on_closing_char, pair_close = False, False + if line is None: + return on_closing_char, pair_close + if cursor_offset < len(line): + cur_char = line[cursor_offset] + if cur_char in CHARACTER_PAIR_MAP.values(): + on_closing_char = True if not ch else cur_char == ch + if cursor_offset > 0: + prev_char = line[cursor_offset - 1] + if ( + on_closing_char + and prev_char in CHARACTER_PAIR_MAP + and CHARACTER_PAIR_MAP[prev_char] == cur_char + ): + pair_close = True if not ch else prev_char == ch + return on_closing_char, pair_close diff --git a/bpython/sample-config b/bpython/sample-config index 287e8a2fc..03127f1b2 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -61,6 +61,8 @@ # Enable autoreload feature by default (default: False). # default_autoreload = False +# Enable autocompletion of brackets and quotes (default: False) +# brackets_completion = False [keyboard] diff --git a/bpython/test/test_brackets_completion.py b/bpython/test/test_brackets_completion.py new file mode 100644 index 000000000..fd9836650 --- /dev/null +++ b/bpython/test/test_brackets_completion.py @@ -0,0 +1,149 @@ +import os + +from bpython.test import FixLanguageTestCase as TestCase, TEST_CONFIG +from bpython.curtsiesfrontend import repl as curtsiesrepl +from bpython import config + + +def setup_config(conf): + config_struct = config.Config(TEST_CONFIG) + for key, value in conf.items(): + if not hasattr(config_struct, key): + raise ValueError(f"{key!r} is not a valid config attribute") + setattr(config_struct, key, value) + return config_struct + + +def create_repl(brackets_enabled=False, **kwargs): + config = setup_config( + {"editor": "true", "brackets_completion": brackets_enabled} + ) + repl = curtsiesrepl.BaseRepl(config, **kwargs) + os.environ["PAGER"] = "true" + os.environ.pop("PYTHONSTARTUP", None) + repl.width = 50 + repl.height = 20 + return repl + + +class TestBracketCompletionEnabled(TestCase): + def setUp(self): + self.repl = create_repl(brackets_enabled=True) + + def process_multiple_events(self, event_list): + for event in event_list: + self.repl.process_event(event) + + def test_start_line(self): + self.repl.process_event("(") + self.assertEqual(self.repl._current_line, "()") + self.assertEqual(self.repl._cursor_offset, 1) + + def test_nested_brackets(self): + self.process_multiple_events(["(", "[", "{"]) + self.assertEqual(self.repl._current_line, """([{}])""") + self.assertEqual(self.repl._cursor_offset, 3) + + def test_quotes(self): + self.process_multiple_events(["(", "'", "x", "", ","]) + self.process_multiple_events(["[", '"', "y", "", "", ""]) + self.assertEqual(self.repl._current_line, """('x',["y"])""") + self.assertEqual(self.repl._cursor_offset, 11) + + def test_bracket_overwrite_closing_char(self): + self.process_multiple_events(["(", "[", "{"]) + self.assertEqual(self.repl._current_line, """([{}])""") + self.assertEqual(self.repl._cursor_offset, 3) + self.process_multiple_events(["}", "]", ")"]) + self.assertEqual(self.repl._current_line, """([{}])""") + self.assertEqual(self.repl._cursor_offset, 6) + + def test_brackets_move_cursor_on_tab(self): + self.process_multiple_events(["(", "[", "{"]) + self.assertEqual(self.repl._current_line, """([{}])""") + self.assertEqual(self.repl._cursor_offset, 3) + self.repl.process_event("") + self.assertEqual(self.repl._current_line, """([{}])""") + self.assertEqual(self.repl._cursor_offset, 4) + self.repl.process_event("") + self.assertEqual(self.repl._current_line, """([{}])""") + self.assertEqual(self.repl._cursor_offset, 5) + self.repl.process_event("") + self.assertEqual(self.repl._current_line, """([{}])""") + self.assertEqual(self.repl._cursor_offset, 6) + + def test_brackets_non_whitespace_following_char(self): + self.repl.current_line = "s = s.connect('localhost', 8080)" + self.repl.cursor_offset = 14 + self.repl.process_event("(") + self.assertEqual( + self.repl._current_line, "s = s.connect(('localhost', 8080)" + ) + self.assertEqual(self.repl._cursor_offset, 15) + + def test_brackets_deletion_on_backspace(self): + self.repl.current_line = "def foo()" + self.repl.cursor_offset = 8 + self.repl.process_event("") + self.assertEqual(self.repl._current_line, "def foo") + self.assertEqual(self.repl.cursor_offset, 7) + + def test_brackets_deletion_on_backspace_nested(self): + self.repl.current_line = '([{""}])' + self.repl.cursor_offset = 4 + self.process_multiple_events( + ["", "", ""] + ) + self.assertEqual(self.repl._current_line, "()") + self.assertEqual(self.repl.cursor_offset, 1) + + +class TestBracketCompletionDisabled(TestCase): + def setUp(self): + self.repl = create_repl(brackets_enabled=False) + + def process_multiple_events(self, event_list): + for event in event_list: + self.repl.process_event(event) + + def test_start_line(self): + self.repl.process_event("(") + self.assertEqual(self.repl._current_line, "(") + self.assertEqual(self.repl._cursor_offset, 1) + + def test_nested_brackets(self): + self.process_multiple_events(["(", "[", "{"]) + self.assertEqual(self.repl._current_line, "([{") + self.assertEqual(self.repl._cursor_offset, 3) + + def test_bracket_overwrite_closing_char(self): + self.process_multiple_events(["(", "[", "{"]) + self.assertEqual(self.repl._current_line, """([{""") + self.assertEqual(self.repl._cursor_offset, 3) + self.process_multiple_events(["}", "]", ")"]) + self.assertEqual(self.repl._current_line, """([{}])""") + self.assertEqual(self.repl._cursor_offset, 6) + + def test_brackets_move_cursor_on_tab(self): + self.process_multiple_events(["(", "[", "{"]) + self.assertEqual(self.repl._current_line, """([{""") + self.assertEqual(self.repl._cursor_offset, 3) + self.repl.process_event("") + self.assertEqual(self.repl._current_line, """([{""") + self.assertEqual(self.repl._cursor_offset, 3) + + def test_brackets_deletion_on_backspace(self): + self.repl.current_line = "def foo()" + self.repl.cursor_offset = 8 + self.repl.process_event("") + self.assertEqual(self.repl._current_line, "def foo") + self.assertEqual(self.repl.cursor_offset, 7) + + def test_brackets_deletion_on_backspace_nested(self): + self.repl.current_line = '([{""}])' + self.repl.cursor_offset = 4 + self.process_multiple_events( + ["", "", ""] + ) + self.assertEqual(self.repl._current_line, "()") + self.assertEqual(self.repl.cursor_offset, 1) From bdd3ab287ac1d2229944000286b689ab48614f6b Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 9 Nov 2021 11:05:07 -0500 Subject: [PATCH 1349/1650] Add brackets auto-close to CHANGELOG --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 42043ea0b..b3700a45f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,8 @@ Changelog General information: New features: +* Auto-closing brackets option added. To enable, add `brackets_completion = True` in the bpython config (press F3 to create) + Thanks to samuelgregorovic Fixes: From 7702ebd0a632f1b0e0afdc83598f48446c893b20 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 9 Nov 2021 17:09:29 +0100 Subject: [PATCH 1350/1650] Follow first parent on merge commits If a bugfix release is merged into main, git describe might use the release tag instead of the X-dev tag. During development, we always want it to use the X-dev tag, however. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 42dd2a309..12d4eeec5 100755 --- a/setup.py +++ b/setup.py @@ -78,7 +78,7 @@ def git_describe_to_python_version(version): try: # get version from git describe proc = subprocess.Popen( - ["git", "describe", "--tags"], + ["git", "describe", "--tags", "--first-parent"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) From adf19b18a5b8814db04c66dfce671957831f2cf9 Mon Sep 17 00:00:00 2001 From: Frostmaine <52504177+Frostmaine@users.noreply.github.com> Date: Tue, 9 Nov 2021 11:16:51 -0500 Subject: [PATCH 1351/1650] added type references to pager.py (#936) --- bpython/_internal.py | 2 +- bpython/pager.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bpython/_internal.py b/bpython/_internal.py index 35cd0bf80..bfcfce46f 100644 --- a/bpython/_internal.py +++ b/bpython/_internal.py @@ -4,7 +4,7 @@ from .pager import page # Ugly monkeypatching -pydoc.pager = page +pydoc.pager = page # type: ignore class _Helper: diff --git a/bpython/pager.py b/bpython/pager.py index e481e7936..673e902bc 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -20,6 +20,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +# mypy: disallow_untyped_defs=True +# mypy: disallow_untyped_calls=True import curses import errno @@ -28,14 +30,15 @@ import subprocess import sys import shlex +from typing import List -def get_pager_command(default="less -rf"): +def get_pager_command(default: str = "less -rf") -> List[str]: command = shlex.split(os.environ.get("PAGER", default)) return command -def page_internal(data): +def page_internal(data: str) -> None: """A more than dumb pager function.""" if hasattr(pydoc, "ttypager"): pydoc.ttypager(data) @@ -43,7 +46,7 @@ def page_internal(data): sys.stdout.write(data) -def page(data, use_internal=False): +def page(data: str, use_internal: bool = False) -> None: command = get_pager_command() if not command or use_internal: page_internal(data) @@ -51,8 +54,9 @@ def page(data, use_internal=False): curses.endwin() try: popen = subprocess.Popen(command, stdin=subprocess.PIPE) - data = data.encode(sys.__stdout__.encoding, "replace") - popen.stdin.write(data) + assert popen.stdin is not None + data_bytes = data.encode(sys.__stdout__.encoding, "replace") + popen.stdin.write(data_bytes) popen.stdin.close() except OSError as e: if e.errno == errno.ENOENT: From 699ced03e8e58c918870ece1af164c10aeb30bb0 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 9 Nov 2021 11:29:02 -0500 Subject: [PATCH 1352/1650] Use pypy3.7 in CI 'pypy3' was using pypy3.6 --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 05e3f65d4..306565fbf 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,10 +10,10 @@ on: jobs: build: runs-on: ubuntu-latest - continue-on-error: ${{ matrix.python-version == 'pypy3' }} + continue-on-error: ${{ matrix.python-version == 'pypy3.7' }} strategy: matrix: - python-version: [3.7, 3.8, 3.9, "3.10", pypy3] + python-version: [3.7, 3.8, 3.9, "3.10", pypy3.7] steps: - uses: actions/checkout@v2 with: From a9db8370ffe55c5461b488e311914eaa2b6f2cbc Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Tue, 9 Nov 2021 11:34:40 -0500 Subject: [PATCH 1353/1650] use pypy-3.7 --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 306565fbf..62ebac0f7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,10 +10,10 @@ on: jobs: build: runs-on: ubuntu-latest - continue-on-error: ${{ matrix.python-version == 'pypy3.7' }} + continue-on-error: ${{ matrix.python-version == 'pypy-3.7' }} strategy: matrix: - python-version: [3.7, 3.8, 3.9, "3.10", pypy3.7] + python-version: [3.7, 3.8, 3.9, "3.10", "pypy-3.7"] steps: - uses: actions/checkout@v2 with: From 83ca33e02fb54640ddd0ff6b956059225b05586c Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Wed, 17 Nov 2021 19:36:13 -0500 Subject: [PATCH 1354/1650] Yield from import module discovery Avoid interruptions by yielding more frequently. This prevent unresponsiveness while working through portions of directory trees without any Python modules. --- bpython/importcompletion.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 00bf99f31..c5dc28f00 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -155,7 +155,9 @@ def complete(self, cursor_offset: int, line: str) -> Optional[Set[str]]: else: return None - def find_modules(self, path: Path) -> Generator[str, None, None]: + def find_modules( + self, path: Path + ) -> Generator[Union[str, None], None, None]: """Find all modules (and packages) for a given directory.""" if not path.is_dir(): # Perhaps a zip file @@ -219,9 +221,12 @@ def find_modules(self, path: Path) -> Generator[str, None, None]: if (stat.st_dev, stat.st_ino) not in self.paths: self.paths.add((stat.st_dev, stat.st_ino)) for subname in self.find_modules(path_real): - if subname != "__init__": + if subname is None: + yield None # take a break to avoid unresponsiveness + elif subname != "__init__": yield f"{name}.{subname}" yield name + yield None # take a break to avoid unresponsiveness def find_all_modules( self, paths: Iterable[Path] @@ -231,7 +236,8 @@ def find_all_modules( for p in paths: for module in self.find_modules(p): - self.modules.add(module) + if module is not None: + self.modules.add(module) yield def find_coroutine(self) -> Optional[bool]: From f3f9e420a9c61b19dbb7aba6274bb43185f8ddd3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 21 Nov 2021 17:38:37 +0100 Subject: [PATCH 1355/1650] Add brackets_completion documentation to configuration-options --- doc/sphinx/source/configuration-options.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/sphinx/source/configuration-options.rst b/doc/sphinx/source/configuration-options.rst index 4d13cbae5..1521542ed 100644 --- a/doc/sphinx/source/configuration-options.rst +++ b/doc/sphinx/source/configuration-options.rst @@ -22,6 +22,13 @@ characters (default: simple). None disables autocompletion. .. versionadded:: 0.12 +brackets_completion +^^^^^^^^^^^^^^^^^^^ +Whether opening character of the pairs ``()``, ``[]``, ``""``, and ``''`` should be auto-closed +(default: False). + +.. versionadded:: 0.23 + .. _configuration_color_scheme: color_scheme @@ -173,7 +180,7 @@ Soft tab size (default 4, see PEP-8). unicode_box ^^^^^^^^^^^ -Whether to use Unicode characters to draw boxes. +Whether to use Unicode characters to draw boxes (default: True). .. versionadded:: 0.14 From 51ebc86070c7a49abe78ba87a0e8268a09f141a6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 8 Dec 2021 18:18:17 +0100 Subject: [PATCH 1356/1650] Add a typing compat module to avoid a dependency on typing-extensions for Py >= 3.8 --- bpython/_typing_compat.py | 33 +++++++++++++++++++++++++++ bpython/curtsies.py | 2 +- bpython/curtsiesfrontend/_internal.py | 2 +- bpython/curtsiesfrontend/repl.py | 2 +- bpython/filelock.py | 2 +- bpython/inspection.py | 2 +- bpython/repl.py | 2 +- setup.cfg | 2 +- 8 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 bpython/_typing_compat.py diff --git a/bpython/_typing_compat.py b/bpython/_typing_compat.py new file mode 100644 index 000000000..31fb64287 --- /dev/null +++ b/bpython/_typing_compat.py @@ -0,0 +1,33 @@ +# The MIT License +# +# Copyright (c) 2021 Sebastian Ramacher +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +try: + # introduced in Python 3.8 + from typing import Literal +except ImportError: + from typing_extensions import Literal # type: ignore + +try: + # introduced in Python 3.8 + from typing import Protocol +except ImportError: + from typing_extensions import Protocol # type: ignore diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 1d41c3b5c..7ffe90109 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -33,7 +33,7 @@ Tuple, Union, ) -from typing_extensions import Protocol +from ._typing_compat import Protocol logger = logging.getLogger(__name__) diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 633174a88..79c5e974c 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -23,7 +23,7 @@ import pydoc from types import TracebackType from typing import Optional, Type -from typing_extensions import Literal +from .._typing_compat import Literal from .. import _internal diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 6693c8517..892dc5289 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -14,7 +14,7 @@ from enum import Enum from types import TracebackType from typing import Dict, Any, List, Optional, Tuple, Union, cast, Type -from typing_extensions import Literal +from .._typing_compat import Literal import blessings import greenlet diff --git a/bpython/filelock.py b/bpython/filelock.py index 6558fc583..429f708b6 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -21,7 +21,7 @@ # THE SOFTWARE. from typing import Optional, Type, IO -from typing_extensions import Literal +from ._typing_compat import Literal from types import TracebackType has_fcntl = True diff --git a/bpython/inspection.py b/bpython/inspection.py index e7ab0a155..21193ef32 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -28,7 +28,7 @@ from collections import namedtuple from typing import Any, Optional, Type from types import MemberDescriptorType, TracebackType -from typing_extensions import Literal +from ._typing_compat import Literal from pygments.token import Token from pygments.lexers import Python3Lexer diff --git a/bpython/repl.py b/bpython/repl.py index e1a5429d5..a071d8b5c 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -38,7 +38,7 @@ from pathlib import Path from types import ModuleType, TracebackType from typing import cast, Tuple, Any, Optional, Type -from typing_extensions import Literal +from ._typing_compat import Literal from pygments.lexers import Python3Lexer from pygments.token import Token diff --git a/setup.cfg b/setup.cfg index 96ef47be1..f5c1bc849 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,7 +27,7 @@ install_requires = pygments pyxdg requests - typing-extensions + typing-extensions; python_version < "3.8" [options.extras_require] clipboard = pyperclip From edb8391f3c48e6ad76cb972362d1032cdb354d82 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 8 Dec 2021 18:54:36 +0100 Subject: [PATCH 1357/1650] Remove unused function --- bpython/repl.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index a071d8b5c..719baec51 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -126,9 +126,6 @@ def __init__(self, locals=None, encoding=None): super().__init__(locals) self.timer = RuntimeTimer() - def reset_running_time(self): - self.running_time = 0 - def runsource(self, source, filename=None, symbol="single", encode="auto"): """Execute Python code. From 1b3c87eade6d7e75ae37c699bcd99424068cc762 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 8 Dec 2021 18:54:50 +0100 Subject: [PATCH 1358/1650] Add type annotations for RuntimeTimer --- bpython/repl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 719baec51..5802d7d7f 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -63,11 +63,11 @@ class RuntimeTimer: """Calculate running time""" - def __init__(self): + def __init__(self) -> None: self.reset_timer() self.time = time.monotonic if hasattr(time, "monotonic") else time.time - def __enter__(self): + def __enter__(self) -> None: self.start = self.time() def __exit__( @@ -80,11 +80,11 @@ def __exit__( self.running_time += self.last_command return False - def reset_timer(self): + def reset_timer(self) -> None: self.running_time = 0.0 self.last_command = 0.0 - def estimate(self): + def estimate(self) -> float: return self.running_time - self.last_command From d0cdeb4926317af49fc791ff5a83efc0382fa242 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 8 Dec 2021 20:24:25 +0100 Subject: [PATCH 1359/1650] Directly use time.monotonic It's always available starting with Python 3.5. --- bpython/repl.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 5802d7d7f..cbfc987e6 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -65,10 +65,9 @@ class RuntimeTimer: def __init__(self) -> None: self.reset_timer() - self.time = time.monotonic if hasattr(time, "monotonic") else time.time def __enter__(self) -> None: - self.start = self.time() + self.start = time.monotonic() def __exit__( self, @@ -76,7 +75,7 @@ def __exit__( exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: - self.last_command = self.time() - self.start + self.last_command = time.monotonic() - self.start self.running_time += self.last_command return False From a96d6e3483e7743fd8e0453532b93154fe0772f6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 8 Dec 2021 19:20:49 +0100 Subject: [PATCH 1360/1650] Reset correct members --- bpython/repl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index cbfc987e6..9a60300cc 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -234,9 +234,9 @@ def __init__(self): # which word is currently replacing the current word self.index = -1 # cursor position in the original line - self.orig_cursor_offset = None + self.orig_cursor_offset = -1 # original line (before match replacements) - self.orig_line = None + self.orig_line = "" # class describing the current type of completion self.completer = None @@ -327,8 +327,8 @@ def update(self, cursor_offset, current_line, matches, completer): def clear(self): self.matches = [] - self.cursor_offset = -1 - self.current_line = "" + self.orig_cursor_offset = -1 + self.orig_line = "" self.current_word = "" self.start = None self.end = None From 9284640d4064c2fcb207e7fc35a5fc4041ba729e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 8 Dec 2021 19:23:41 +0100 Subject: [PATCH 1361/1650] Add type annotations to MatchesIterator --- bpython/repl.py | 50 +++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 9a60300cc..689115ffc 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -37,7 +37,7 @@ from itertools import takewhile from pathlib import Path from types import ModuleType, TracebackType -from typing import cast, Tuple, Any, Optional, Type +from typing import cast, List, Tuple, Any, Optional, Type from ._typing_compat import Literal from pygments.lexers import Python3Lexer @@ -226,11 +226,11 @@ class MatchesIterator: A MatchesIterator can be `clear`ed to reset match iteration, and `update`ed to set what matches will be iterated over.""" - def __init__(self): + def __init__(self) -> None: # word being replaced in the original line of text self.current_word = "" # possible replacements for current_word - self.matches = None + self.matches: List[str] = [] # which word is currently replacing the current word self.index = -1 # cursor position in the original line @@ -238,63 +238,67 @@ def __init__(self): # original line (before match replacements) self.orig_line = "" # class describing the current type of completion - self.completer = None + self.completer: Optional[autocomplete.BaseCompletionType] = None - def __nonzero__(self): + def __nonzero__(self) -> bool: """MatchesIterator is False when word hasn't been replaced yet""" return self.index != -1 - def __bool__(self): + def __bool__(self) -> bool: return self.index != -1 @property - def candidate_selected(self): + def candidate_selected(self) -> bool: """True when word selected/replaced, False when word hasn't been replaced yet""" return bool(self) - def __iter__(self): + def __iter__(self) -> "MatchesIterator": return self - def current(self): + def current(self) -> str: if self.index == -1: raise ValueError("No current match.") return self.matches[self.index] - def __next__(self): + def __next__(self) -> str: self.index = (self.index + 1) % len(self.matches) return self.matches[self.index] - def previous(self): + def previous(self) -> str: if self.index <= 0: self.index = len(self.matches) self.index -= 1 return self.matches[self.index] - def cur_line(self): + def cur_line(self) -> Tuple[int, str]: """Returns a cursor offset and line with the current substitution made""" return self.substitute(self.current()) - def substitute(self, match): + def substitute(self, match) -> Tuple[int, str]: """Returns a cursor offset and line with match substituted in""" - start, end, word = self.completer.locate( + assert self.completer is not None + + start, end, _ = self.completer.locate( self.orig_cursor_offset, self.orig_line - ) + ) # type: ignore return ( start + len(match), self.orig_line[:start] + match + self.orig_line[end:], ) - def is_cseq(self): + def is_cseq(self) -> bool: return bool( os.path.commonprefix(self.matches)[len(self.current_word) :] ) - def substitute_cseq(self): + def substitute_cseq(self) -> Tuple[int, str]: """Returns a new line by substituting a common sequence in, and update matches""" + assert self.completer is not None + cseq = os.path.commonprefix(self.matches) new_cursor_offset, new_line = self.substitute(cseq) if len(self.matches) == 1: @@ -307,7 +311,13 @@ def substitute_cseq(self): self.clear() return new_cursor_offset, new_line - def update(self, cursor_offset, current_line, matches, completer): + def update( + self, + cursor_offset: int, + current_line: str, + matches: List[str], + completer: autocomplete.BaseCompletionType, + ) -> None: """Called to reset the match index and update the word being replaced Should only be called if there's a target to update - otherwise, call @@ -323,9 +333,9 @@ def update(self, cursor_offset, current_line, matches, completer): self.index = -1 self.start, self.end, self.current_word = self.completer.locate( self.orig_cursor_offset, self.orig_line - ) + ) # type: ignore - def clear(self): + def clear(self) -> None: self.matches = [] self.orig_cursor_offset = -1 self.orig_line = "" From 8bf810d6142f4f3e30a18b7ca8912d181ae52322 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 8 Dec 2021 20:24:05 +0100 Subject: [PATCH 1362/1650] Remove unused function --- bpython/repl.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 689115ffc..8cf88962d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1178,20 +1178,6 @@ def next_indentation(line, tab_length): return indentation -def next_token_inside_string(code_string, inside_string): - """Given a code string s and an initial state inside_string, return - whether the next token will be inside a string or not.""" - for token, value in Python3Lexer().get_tokens(code_string): - if token is Token.String: - value = value.lstrip("bBrRuU") - if value in ('"""', "'''", '"', "'"): - if not inside_string: - inside_string = value - elif value == inside_string: - inside_string = False - return inside_string - - def split_lines(tokens): for (token, value) in tokens: if not value: From 3c9e35dc6c38e807116b0b1406df0406fd04f5c0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 8 Dec 2021 22:54:12 +0100 Subject: [PATCH 1363/1650] Add return type for __enter__ --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 21193ef32..5ebfdbe85 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -58,7 +58,7 @@ class AttrCleaner: def __init__(self, obj: Any) -> None: self.obj = obj - def __enter__(self): + def __enter__(self) -> None: """Try to make an object not exhibit side-effects on attribute lookup.""" type_ = type(self.obj) From fdd4ad960351e5cb7e4b77bd891cecc0d638d199 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 31 Dec 2021 13:59:15 +0100 Subject: [PATCH 1364/1650] Fix tests with Python 3.10.1 --- bpython/test/test_interpreter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index ca64de77b..1a5eed393 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -35,7 +35,7 @@ def test_syntaxerror(self): i.runsource("1.1.1.1") - if sys.version_info[:2] >= (3, 10): + if (3, 10, 0) <= sys.version_info[:3] < (3, 10, 1): expected = ( " File " + green('""') @@ -47,7 +47,7 @@ def test_syntaxerror(self): + cyan("invalid syntax. Perhaps you forgot a comma?") + "\n" ) - elif (3, 8) <= sys.version_info[:2] <= (3, 9): + elif (3, 8) <= sys.version_info[:2]: expected = ( " File " + green('""') From b46afa3bb1ab783c96dc80c5184090a171ab70d4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 31 Dec 2021 14:00:46 +0100 Subject: [PATCH 1365/1650] Apply black --- bpython/autocomplete.py | 1 - bpython/urwid.py | 1 - 2 files changed, 2 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 7a65d1b30..71e7fe094 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -626,7 +626,6 @@ def matches( def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: return None - else: class JediCompletion(BaseCompletionType): diff --git a/bpython/urwid.py b/bpython/urwid.py index e3aab75c2..33d1f4b8c 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -128,7 +128,6 @@ def wrapper(*args, **kwargs): return wrapper - else: TwistedEventLoop = getattr(urwid, "TwistedEventLoop", None) From 4510162c67e9b54b1258bb4c47bca0797d51ba3f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 31 Dec 2021 14:07:44 +0100 Subject: [PATCH 1366/1650] Apply black --- bpython/curtsiesfrontend/filewatch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index e3607180c..7616d4845 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -11,7 +11,6 @@ def ModuleChangedEventHandler(*args): return None - else: class ModuleChangedEventHandler(FileSystemEventHandler): # type: ignore [no-redef] From 4d33cc6ef6114fb173452ade774b8995ffc54783 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 31 Dec 2021 14:08:35 +0100 Subject: [PATCH 1367/1650] Really fix tests with Python 3.10.1 --- bpython/test/test_interpreter.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 1a5eed393..45ffa66d6 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -35,7 +35,19 @@ def test_syntaxerror(self): i.runsource("1.1.1.1") - if (3, 10, 0) <= sys.version_info[:3] < (3, 10, 1): + if (3, 10, 1) <= sys.version_info[:3]: + expected = ( + " File " + + green('""') + + ", line " + + bold(magenta("1")) + + "\n 1.1.1.1\n ^^\n" + + bold(red("SyntaxError")) + + ": " + + cyan("invalid syntax") + + "\n" + ) + elif (3, 10) <= sys.version_info[:2]: expected = ( " File " + green('""') From 60baf5eee9c107df5cce2319227fb129ff64bb77 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 8 Jan 2022 20:27:26 +0100 Subject: [PATCH 1368/1650] Fix call of __exit__ (fixes #948) --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 892dc5289..0d45593fb 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -611,7 +611,7 @@ def sigwinch_handler(self, signum, frame): def sigtstp_handler(self, signum, frame): self.scroll_offset = len(self.lines_for_display) - self.__exit__() + self.__exit__(None, None, None) self.on_suspend() os.kill(os.getpid(), signal.SIGTSTP) self.after_suspend() From 9465700bc899eda0c7d71c7baf47ec1c06414511 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 8 Jan 2022 20:33:56 +0100 Subject: [PATCH 1369/1650] Simplify is_closing_quote --- bpython/curtsiesfrontend/repl.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 0d45593fb..758c9745d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -814,16 +814,14 @@ def process_key_event(self, e: str) -> None: else: self.add_normal_character(e) - def is_closing_quote(self, e): + def is_closing_quote(self, e: str) -> bool: char_count = self._current_line.count(e) - if ( + return ( char_count % 2 == 0 and cursor_on_closing_char_pair( self._cursor_offset, self._current_line, e )[0] - ): - return True - return False + ) def insert_char_pair_start(self, e): """Accepts character which is a part of CHARACTER_PAIR_MAP From 30f8dbaa8d38f7b1f3652c21e1cbc6b1ae969f82 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 8 Jan 2022 20:43:03 +0100 Subject: [PATCH 1370/1650] Check for None --- bpython/line.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index cbfa682de..3572e45da 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -300,7 +300,7 @@ def cursor_on_closing_char_pair(cursor_offset, line, ch=None): if cursor_offset < len(line): cur_char = line[cursor_offset] if cur_char in CHARACTER_PAIR_MAP.values(): - on_closing_char = True if not ch else cur_char == ch + on_closing_char = True if ch is None else cur_char == ch if cursor_offset > 0: prev_char = line[cursor_offset - 1] if ( @@ -308,5 +308,5 @@ def cursor_on_closing_char_pair(cursor_offset, line, ch=None): and prev_char in CHARACTER_PAIR_MAP and CHARACTER_PAIR_MAP[prev_char] == cur_char ): - pair_close = True if not ch else prev_char == ch + pair_close = True if ch is None else prev_char == ch return on_closing_char, pair_close From 562ad3f68a25ed805e81755ef26fcb6507614520 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 8 Jan 2022 20:44:01 +0100 Subject: [PATCH 1371/1650] Simplify insert_char_pair_start --- bpython/curtsiesfrontend/repl.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 758c9745d..973654218 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -834,7 +834,6 @@ def insert_char_pair_start(self, e): """ self.add_normal_character(e) if self.config.brackets_completion: - allowed_chars = ["}", ")", "]", " "] start_of_line = len(self._current_line) == 1 end_of_line = len(self._current_line) == self._cursor_offset can_lookup_next = len(self._current_line) > self._cursor_offset @@ -843,10 +842,8 @@ def insert_char_pair_start(self, e): if not can_lookup_next else self._current_line[self._cursor_offset] ) - next_char_allowed = next_char in allowed_chars - if start_of_line or end_of_line or next_char_allowed: - closing_char = CHARACTER_PAIR_MAP[e] - self.add_normal_character(closing_char, narrow_search=False) + if start_of_line or end_of_line or next_char in "})] ": + self.add_normal_character(CHARACTER_PAIR_MAP[e], narrow_search=False) self._cursor_offset -= 1 def insert_char_pair_end(self, e): From 94bda61004598cea8b988ecc4067c494f8901c69 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 9 Jan 2022 00:19:29 +0100 Subject: [PATCH 1372/1650] Apply black --- bpython/curtsiesfrontend/repl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 973654218..018904527 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -816,7 +816,7 @@ def process_key_event(self, e: str) -> None: def is_closing_quote(self, e: str) -> bool: char_count = self._current_line.count(e) - return ( + return ( char_count % 2 == 0 and cursor_on_closing_char_pair( self._cursor_offset, self._current_line, e @@ -843,7 +843,9 @@ def insert_char_pair_start(self, e): else self._current_line[self._cursor_offset] ) if start_of_line or end_of_line or next_char in "})] ": - self.add_normal_character(CHARACTER_PAIR_MAP[e], narrow_search=False) + self.add_normal_character( + CHARACTER_PAIR_MAP[e], narrow_search=False + ) self._cursor_offset -= 1 def insert_char_pair_end(self, e): From 189da3ecbaa30212b8ba73aeb321b6a6a324348b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 9 Jan 2022 00:22:52 +0100 Subject: [PATCH 1373/1650] Add type annotations --- bpython/line.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 3572e45da..ee964a088 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -7,7 +7,7 @@ import re from itertools import chain -from typing import Optional, NamedTuple +from typing import Optional, NamedTuple, Tuple from .lazyre import LazyReCompile @@ -290,7 +290,9 @@ def current_expression_attribute( return None -def cursor_on_closing_char_pair(cursor_offset, line, ch=None): +def cursor_on_closing_char_pair( + cursor_offset: int, line: str, ch: Optional[str] = None +) -> Tuple[bool, bool]: """Checks if cursor sits on closing character of a pair and whether its pair character is directly behind it """ From 95539ccdf8ccc1b614a62a757044c08a308a5375 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 9 Jan 2022 16:11:05 +0100 Subject: [PATCH 1374/1650] Avoid indexing and tuple unpacking of LinePart instances --- bpython/autocomplete.py | 2 +- bpython/importcompletion.py | 12 +++++----- bpython/line.py | 23 ++++++++---------- bpython/repl.py | 21 +++++++++------- bpython/test/test_autocomplete.py | 5 +++- bpython/test/test_line_properties.py | 36 ++++++++++++++++++---------- bpython/test/test_repl.py | 12 ++++++---- 7 files changed, 64 insertions(+), 47 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 71e7fe094..bb8a8b0c7 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -481,7 +481,7 @@ def matches( if current_dict_parts is None: return None - _, _, dexpr = current_dict_parts + dexpr = current_dict_parts.word try: obj = safe_eval(dexpr, locals_) except EvaluationError: diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index c5dc28f00..969361447 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -135,22 +135,22 @@ def complete(self, cursor_offset: int, line: str) -> Optional[Set[str]]: if import_import is not None: # `from a import ` completion matches = self.module_matches( - import_import[2], from_import_from[2] + import_import.word, from_import_from.word ) matches.update( - self.attr_matches(import_import[2], from_import_from[2]) + self.attr_matches(import_import.word, from_import_from.word) ) else: # `from ` completion - matches = self.module_attr_matches(from_import_from[2]) - matches.update(self.module_matches(from_import_from[2])) + matches = self.module_attr_matches(from_import_from.word) + matches.update(self.module_matches(from_import_from.word)) return matches cur_import = current_import(cursor_offset, line) if cur_import is not None: # `import ` completion - matches = self.module_matches(cur_import[2]) - matches.update(self.module_attr_matches(cur_import[2])) + matches = self.module_matches(cur_import.word) + matches.update(self.module_attr_matches(cur_import.word)) return matches else: return None diff --git a/bpython/line.py b/bpython/line.py index ee964a088..99615efe5 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -130,15 +130,14 @@ def current_object(cursor_offset: int, line: str) -> Optional[LinePart]: match = current_word(cursor_offset, line) if match is None: return None - start, end, word = match s = ".".join( m.group(1) - for m in _current_object_re.finditer(word) - if m.end(1) + start < cursor_offset + for m in _current_object_re.finditer(match.word) + if m.end(1) + match.start < cursor_offset ) if not s: return None - return LinePart(start, start + len(s), s) + return LinePart(match.start, match.start + len(s), s) _current_object_attribute_re = LazyReCompile(r"([\w_][\w0-9_]*)[.]?") @@ -152,12 +151,13 @@ def current_object_attribute( match = current_word(cursor_offset, line) if match is None: return None - start, end, word = match - matches = _current_object_attribute_re.finditer(word) + matches = _current_object_attribute_re.finditer(match.word) next(matches) for m in matches: - if m.start(1) + start <= cursor_offset <= m.end(1) + start: - return LinePart(m.start(1) + start, m.end(1) + start, m.group(1)) + if m.start(1) + match.start <= cursor_offset <= m.end(1) + match.start: + return LinePart( + m.start(1) + match.start, m.end(1) + match.start, m.group(1) + ) return None @@ -266,11 +266,8 @@ def current_dotted_attribute( ) -> Optional[LinePart]: """The dotted attribute-object pair before the cursor""" match = current_word(cursor_offset, line) - if match is None: - return None - start, end, word = match - if "." in word[1:]: - return LinePart(start, end, word) + if match is not None and "." in match.word[1:]: + return match return None diff --git a/bpython/repl.py b/bpython/repl.py index 8cf88962d..4ee4eebae 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -239,6 +239,8 @@ def __init__(self) -> None: self.orig_line = "" # class describing the current type of completion self.completer: Optional[autocomplete.BaseCompletionType] = None + self.start: Optional[int] = None + self.end: Optional[int] = None def __nonzero__(self) -> bool: """MatchesIterator is False when word hasn't been replaced yet""" @@ -277,16 +279,15 @@ def cur_line(self) -> Tuple[int, str]: made""" return self.substitute(self.current()) - def substitute(self, match) -> Tuple[int, str]: + def substitute(self, match: str) -> Tuple[int, str]: """Returns a cursor offset and line with match substituted in""" assert self.completer is not None - start, end, _ = self.completer.locate( - self.orig_cursor_offset, self.orig_line - ) # type: ignore + lp = self.completer.locate(self.orig_cursor_offset, self.orig_line) + assert lp is not None return ( - start + len(match), - self.orig_line[:start] + match + self.orig_line[end:], + lp.start + len(match), + self.orig_line[: lp.start] + match + self.orig_line[lp.stop :], ) def is_cseq(self) -> bool: @@ -331,9 +332,11 @@ def update( self.matches = matches self.completer = completer self.index = -1 - self.start, self.end, self.current_word = self.completer.locate( - self.orig_cursor_offset, self.orig_line - ) # type: ignore + lp = self.completer.locate(self.orig_cursor_offset, self.orig_line) + assert lp is not None + self.start = lp.start + self.end = lp.stop + self.current_word = lp.word def clear(self) -> None: self.matches = [] diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index ad991abe4..8b171d035 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -12,6 +12,7 @@ has_jedi = False from bpython import autocomplete +from bpython.line import LinePart glob_function = "glob.iglob" @@ -114,7 +115,9 @@ def test_locate_fails_when_not_in_string(self): self.assertEqual(self.completer.locate(4, "abcd"), None) def test_locate_succeeds_when_in_string(self): - self.assertEqual(self.completer.locate(4, "a'bc'd"), (2, 4, "bc")) + self.assertEqual( + self.completer.locate(4, "a'bc'd"), LinePart(2, 4, "bc") + ) def test_issue_491(self): self.assertNotEqual(self.completer.matches(9, '"a[a.l-1]'), None) diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 592a61765..967ecbe01 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -1,7 +1,9 @@ import re +from typing import Optional, Tuple import unittest from bpython.line import ( + LinePart, current_word, current_dict_key, current_dict, @@ -25,7 +27,7 @@ def cursor(s): return cursor_offset, line -def decode(s): +def decode(s: str) -> Tuple[Tuple[int, str], Optional[LinePart]]: """'ad' -> ((3, 'abcd'), (1, 3, 'bdc'))""" if not s.count("|") == 1: @@ -41,16 +43,16 @@ def decode(s): assert len(d) in [1, 3], "need all the parts just once! %r" % d if "<" in d: - return (d["|"], s), (d["<"], d[">"], s[d["<"] : d[">"]]) + return (d["|"], s), LinePart(d["<"], d[">"], s[d["<"] : d[">"]]) else: return (d["|"], s), None -def line_with_cursor(cursor_offset, line): +def line_with_cursor(cursor_offset: int, line: str) -> str: return line[:cursor_offset] + "|" + line[cursor_offset:] -def encode(cursor_offset, line, result): +def encode(cursor_offset: int, line: str, result: Optional[LinePart]) -> str: """encode(3, 'abdcd', (1, 3, 'bdc')) -> ad' Written for prettier assert error messages @@ -58,7 +60,9 @@ def encode(cursor_offset, line, result): encoded_line = line_with_cursor(cursor_offset, line) if result is None: return encoded_line - start, end, value = result + start = result.start + end = result.stop + value = result.word assert line[start:end] == value if start < cursor_offset: encoded_line = encoded_line[:start] + "<" + encoded_line[start:] @@ -107,19 +111,25 @@ def test_I(self): self.assertEqual(cursor("asd|fgh"), (3, "asdfgh")) def test_decode(self): - self.assertEqual(decode("ad"), ((3, "abdcd"), (1, 4, "bdc"))) - self.assertEqual(decode("a|d"), ((1, "abdcd"), (1, 4, "bdc"))) - self.assertEqual(decode("ad|"), ((5, "abdcd"), (1, 4, "bdc"))) + self.assertEqual( + decode("ad"), ((3, "abdcd"), LinePart(1, 4, "bdc")) + ) + self.assertEqual( + decode("a|d"), ((1, "abdcd"), LinePart(1, 4, "bdc")) + ) + self.assertEqual( + decode("ad|"), ((5, "abdcd"), LinePart(1, 4, "bdc")) + ) def test_encode(self): - self.assertEqual(encode(3, "abdcd", (1, 4, "bdc")), "ad") - self.assertEqual(encode(1, "abdcd", (1, 4, "bdc")), "a|d") - self.assertEqual(encode(4, "abdcd", (1, 4, "bdc")), "ad") - self.assertEqual(encode(5, "abdcd", (1, 4, "bdc")), "ad|") + self.assertEqual(encode(3, "abdcd", LinePart(1, 4, "bdc")), "ad") + self.assertEqual(encode(1, "abdcd", LinePart(1, 4, "bdc")), "a|d") + self.assertEqual(encode(4, "abdcd", LinePart(1, 4, "bdc")), "ad") + self.assertEqual(encode(5, "abdcd", LinePart(1, 4, "bdc")), "ad|") def test_assert_access(self): def dumb_func(cursor_offset, line): - return (0, 2, "ab") + return LinePart(0, 2, "ab") self.func = dumb_func self.assertAccess("d") diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index e29c5a4e5..a4241087d 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -10,8 +10,12 @@ from unittest import mock from bpython import config, repl, cli, autocomplete -from bpython.test import MagicIterMock, FixLanguageTestCase as TestCase -from bpython.test import TEST_CONFIG +from bpython.line import LinePart +from bpython.test import ( + MagicIterMock, + FixLanguageTestCase as TestCase, + TEST_CONFIG, +) pypy = "PyPy" in sys.version @@ -99,7 +103,7 @@ def test_update(self): newmatches = ["string", "str", "set"] completer = mock.Mock() - completer.locate.return_value = (0, 1, "s") + completer.locate.return_value = LinePart(0, 1, "s") self.matches_iterator.update(1, "s", newmatches, completer) newslice = islice(newmatches, 0, 3) @@ -108,7 +112,7 @@ def test_update(self): def test_cur_line(self): completer = mock.Mock() - completer.locate.return_value = ( + completer.locate.return_value = LinePart( 0, self.matches_iterator.orig_cursor_offset, self.matches_iterator.orig_line, From 809003c24bfe0577a936670355e3856388eb77b4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 9 Jan 2022 15:28:33 +0100 Subject: [PATCH 1375/1650] Replace NamedTuple with dataclass --- bpython/line.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/line.py b/bpython/line.py index 99615efe5..cbc3bf37e 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -6,13 +6,15 @@ import re +from dataclasses import dataclass from itertools import chain -from typing import Optional, NamedTuple, Tuple +from typing import Optional, Tuple from .lazyre import LazyReCompile -class LinePart(NamedTuple): +@dataclass +class LinePart: start: int stop: int word: str From a60ae154152c4199336be70a53df0091a9061832 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 9 Jan 2022 23:22:38 +0100 Subject: [PATCH 1376/1650] Fix type annotation --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index bb8a8b0c7..916a62291 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -63,7 +63,7 @@ class AutocompleteModes(Enum): FUZZY = "fuzzy" @classmethod - def from_string(cls, value: str) -> Optional[Any]: + def from_string(cls, value: str) -> Optional["AutocompleteModes"]: if value.upper() in cls.__members__: return cls.__members__[value.upper()] return None From 5629bbe2225976defd628b4063fe61ec8595909b Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Mon, 3 Jan 2022 22:11:08 -0600 Subject: [PATCH 1377/1650] Adding type hints to formatter.py --- bpython/formatter.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/bpython/formatter.py b/bpython/formatter.py index 9618979aa..f216f213f 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -24,9 +24,14 @@ # Pygments really kicks ass, it made it really easy to # get the exact behaviour I wanted, thanks Pygments.:) +# mypy: disallow_untyped_defs=True +# mypy: disallow_untyped_calls=True + +from typing import Any, MutableMapping, Iterable, TextIO from pygments.formatter import Formatter from pygments.token import ( + _TokenType, Keyword, Name, Comment, @@ -96,7 +101,9 @@ class BPythonFormatter(Formatter): See the Pygments source for more info; it's pretty straightforward.""" - def __init__(self, color_scheme, **options): + def __init__( + self, color_scheme: MutableMapping[str, str], **options: Any + ) -> None: self.f_strings = {} for k, v in theme_map.items(): self.f_strings[k] = f"\x01{color_scheme[v]}" @@ -106,14 +113,21 @@ def __init__(self, color_scheme, **options): self.f_strings[k] += "I" super().__init__(**options) - def format(self, tokensource, outfile): - o = "" + def format( + self, + tokensource: Iterable[MutableMapping[_TokenType, str]], + outfile: TextIO, + ) -> None: + o: str = "" for token, text in tokensource: if text == "\n": continue while token not in self.f_strings: - token = token.parent + if token.parent is None: + break + else: + token = token.parent o += f"{self.f_strings[token]}\x03{text}\x04" outfile.write(o.rstrip()) From f8ce9162bfccc20b978536c581192984cf43f87f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 2 Feb 2022 14:41:22 +0100 Subject: [PATCH 1378/1650] Mark optional arguments as optional --- bpython/curtsiesfrontend/interpreter.py | 8 ++++++-- bpython/curtsiesfrontend/repl.py | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 48ee15bae..91dba96ae 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,5 +1,5 @@ import sys -from typing import Any, Dict +from typing import Any, Dict, Optional from pygments.token import Generic, Token, Keyword, Name, Comment, String from pygments.token import Error, Literal, Number, Operator, Punctuation @@ -60,7 +60,11 @@ def format(self, tokensource, outfile): class Interp(ReplInterpreter): - def __init__(self, locals: Dict[str, Any] = None, encoding=None): + def __init__( + self, + locals: Optional[Dict[str, Any]] = None, + encoding: Optional[str] = None, + ) -> None: """Constructor. We include an argument for the outfile to pass to the formatter for it diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 018904527..59dcd481d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -312,10 +312,10 @@ class BaseRepl(Repl): def __init__( self, config: Config, - locals_: Dict[str, Any] = None, - banner: str = None, - interp: code.InteractiveInterpreter = None, - orig_tcattrs: List[Any] = None, + locals_: Optional[Dict[str, Any]] = None, + banner: Optional[str] = None, + interp: Optional[code.InteractiveInterpreter] = None, + orig_tcattrs: Optional[List[Any]] = None, ): """ locals_ is a mapping of locals to pass into the interpreter From 6005b72b1065f71290008b6e880c56e7256ce0f2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 2 Feb 2022 14:54:49 +0100 Subject: [PATCH 1379/1650] Replace a loop with an rfind --- bpython/autocomplete.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 916a62291..81cea8945 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -398,13 +398,10 @@ def matches( assert "." in r.word - for i in range(1, len(r.word) + 1): - if r.word[-i] == "[": - i -= 1 - break - methodtext = r.word[-i:] + i = r.word.rfind("[") + 1 + methodtext = r.word[i:] matches = { - "".join([r.word[:-i], m]) + "".join([r.word[:i], m]) for m in self.attr_matches(methodtext, locals_) } From 2dd27f9eacf893042fee0a6789c10241460e3915 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 3 Feb 2022 23:03:03 +0100 Subject: [PATCH 1380/1650] Add type check instead of using exceptions --- bpython/patch_linecache.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index e1d94a157..f947f7c4f 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -53,10 +53,10 @@ def __delitem__(self, key): return super().__delitem__(key) -def _bpython_clear_linecache(): - try: +def _bpython_clear_linecache() -> None: + if isinstance(linecache.cache, BPythonLinecache): bpython_history = linecache.cache.bpython_history - except AttributeError: + else: bpython_history = [] linecache.cache = BPythonLinecache() linecache.cache.bpython_history = bpython_history @@ -68,12 +68,12 @@ def _bpython_clear_linecache(): linecache.clearcache = _bpython_clear_linecache -def filename_for_console_input(code_string): +def filename_for_console_input(code_string: str) -> str: """Remembers a string of source code, and returns a fake filename to use to retrieve it later.""" - try: + if isinstance(linecache.cache, BPythonLinecache): return linecache.cache.remember_bpython_input(code_string) - except AttributeError: + else: # If someone else has patched linecache.cache, better for code to # simply be unavailable to inspect.getsource() than to raise # an exception. From 9984c2391f1621c280dd32361f8ea0f712cee8b6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 3 Feb 2022 23:03:15 +0100 Subject: [PATCH 1381/1650] Add type annotations for line cache --- bpython/patch_linecache.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index f947f7c4f..4a89f23a9 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -1,22 +1,23 @@ import linecache +from typing import Any, List, Tuple class BPythonLinecache(dict): """Replaces the cache dict in the standard-library linecache module, to also remember (in an unerasable way) bpython console input.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.bpython_history = [] - def is_bpython_filename(self, fname): + def is_bpython_filename(self, fname: Any) -> bool: try: return fname.startswith(" Tuple[int, None, List[str], str]: """Given a filename provided by remember_bpython_input, returns the associated source string.""" try: @@ -25,21 +26,21 @@ def get_bpython_history(self, key): except (IndexError, ValueError): raise KeyError - def remember_bpython_input(self, source): + def remember_bpython_input(self, source: str) -> str: """Remembers a string of source code, and returns a fake filename to use to retrieve it later.""" - filename = "" % len(self.bpython_history) + filename = f"" self.bpython_history.append( (len(source), None, source.splitlines(True), filename) ) return filename - def __getitem__(self, key): + def __getitem__(self, key: Any) -> Any: if self.is_bpython_filename(key): return self.get_bpython_history(key) return super().__getitem__(key) - def __contains__(self, key): + def __contains__(self, key: Any) -> bool: if self.is_bpython_filename(key): try: self.get_bpython_history(key) @@ -48,9 +49,9 @@ def __contains__(self, key): return False return super().__contains__(key) - def __delitem__(self, key): + def __delitem__(self, key: Any) -> None: if not self.is_bpython_filename(key): - return super().__delitem__(key) + super().__delitem__(key) def _bpython_clear_linecache() -> None: From 92c3d1f6ff64b09ba1a161ea75e8738b4286de8c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 3 Feb 2022 23:20:16 +0100 Subject: [PATCH 1382/1650] Fix mypy regression --- bpython/patch_linecache.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index 4a89f23a9..82b38dd74 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -8,12 +8,12 @@ class BPythonLinecache(dict): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self.bpython_history = [] + self.bpython_history: List[Tuple[int, None, List[str], str]] = [] def is_bpython_filename(self, fname: Any) -> bool: - try: + if isinstance(fname, str): return fname.startswith(" Date: Mon, 25 Apr 2022 22:59:29 +0200 Subject: [PATCH 1383/1650] Avoid a potential import of fcntl --- bpython/config.py | 10 +++++++--- bpython/curtsiesfrontend/parse.py | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index c6cadf85d..29b906ddb 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -1,7 +1,7 @@ # The MIT License # # Copyright (c) 2009-2015 the bpython authors. -# Copyright (c) 2015-2020 Sebastian Ramacher +# Copyright (c) 2015-2022 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -35,9 +35,13 @@ from xdg import BaseDirectory from .autocomplete import AutocompleteModes -from .curtsiesfrontend.parse import CNAMES default_completion = AutocompleteModes.SIMPLE +# All supported letters for colors for themes +# +# Instead of importing it from .curtsiesfrontend.parse, we define them here to +# avoid a potential import of fcntl on Windows. +COLOR_LETTERS = tuple("krgybmcwd") class UnknownColorCode(Exception): @@ -381,7 +385,7 @@ def load_theme( colors[k] = theme.get("syntax", k) else: colors[k] = theme.get("interface", k) - if colors[k].lower() not in CNAMES: + if colors[k].lower() not in COLOR_LETTERS: raise UnknownColorCode(k, colors[k]) # Check against default theme to see if all values are defined diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 6a42b3764..d10a0f5c2 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -8,11 +8,12 @@ ) from functools import partial +from ..config import COLOR_LETTERS from ..lazyre import LazyReCompile COLORS = CURTSIES_COLORS + ("default",) -CNAMES = dict(zip("krgybmcwd", COLORS)) +CNAMES = dict(zip(COLOR_LETTERS, COLORS)) # hack for finding the "inverse" INVERSE_COLORS = { CURTSIES_COLORS[idx]: CURTSIES_COLORS[ From 8c4fd2304da4f4858ce87621d80aebb8c0104072 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 25 Apr 2022 23:11:50 +0200 Subject: [PATCH 1384/1650] Add type annotations --- bpython/curtsiesfrontend/parse.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index d10a0f5c2..13a200ec5 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -1,4 +1,6 @@ import re +from functools import partial +from typing import Any, Callable, Dict, Tuple from curtsies.formatstring import fmtstr, FmtStr from curtsies.termformatconstants import ( @@ -6,7 +8,6 @@ BG_COLORS, colors as CURTSIES_COLORS, ) -from functools import partial from ..config import COLOR_LETTERS from ..lazyre import LazyReCompile @@ -24,7 +25,9 @@ INVERSE_COLORS["default"] = INVERSE_COLORS[CURTSIES_COLORS[0]] -def func_for_letter(letter_color_code: str, default: str = "k"): +def func_for_letter( + letter_color_code: str, default: str = "k" +) -> Callable[..., FmtStr]: """Returns FmtStr constructor for a bpython-style color code""" if letter_color_code == "d": letter_color_code = default @@ -37,13 +40,13 @@ def func_for_letter(letter_color_code: str, default: str = "k"): ) -def color_for_letter(letter_color_code: str, default: str = "k"): +def color_for_letter(letter_color_code: str, default: str = "k") -> str: if letter_color_code == "d": letter_color_code = default return CNAMES[letter_color_code.lower()] -def parse(s): +def parse(s: str) -> FmtStr: """Returns a FmtStr object from a bpython-formatted colored string""" rest = s stuff = [] @@ -59,7 +62,7 @@ def parse(s): ) -def fs_from_match(d): +def fs_from_match(d: Dict[str, Any]) -> FmtStr: atts = {} if d["fg"]: # this isn't according to spec as I understand it @@ -97,7 +100,7 @@ def fs_from_match(d): ) -def peel_off_string(s): +def peel_off_string(s: str) -> Tuple[Dict[str, Any], str]: m = peel_off_string_re.match(s) assert m, repr(s) d = m.groupdict() From f38ced3cb299b43430e3be21d065cb7c6626d648 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 25 Apr 2022 23:12:01 +0200 Subject: [PATCH 1385/1650] Simplify loop condition --- bpython/curtsiesfrontend/parse.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 13a200ec5..8eb1fddd1 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -50,9 +50,7 @@ def parse(s: str) -> FmtStr: """Returns a FmtStr object from a bpython-formatted colored string""" rest = s stuff = [] - while True: - if not rest: - break + while rest: start, rest = peel_off_string(rest) stuff.append(start) return ( From 4c5e18b7c244cad8e21600fae61d6faa3a8ef54b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 25 Apr 2022 23:12:26 +0200 Subject: [PATCH 1386/1650] Apply black --- bpython/test/test_preprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index 03e9a3b8e..ee3f20857 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -15,7 +15,7 @@ def get_fodder_source(test_name): - pattern = fr"#StartTest-{test_name}\n(.*?)#EndTest" + pattern = rf"#StartTest-{test_name}\n(.*?)#EndTest" orig, xformed = [ re.search(pattern, inspect.getsource(module), re.DOTALL) for module in [original, processed] From ace2d066a1e70a710807740739ab325cbe39212c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 25 Apr 2022 23:19:16 +0200 Subject: [PATCH 1387/1650] Ensure that color is always initialized --- bpython/curtsiesfrontend/parse.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 8eb1fddd1..88a149a65 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -62,6 +62,7 @@ def parse(s: str) -> FmtStr: def fs_from_match(d: Dict[str, Any]) -> FmtStr: atts = {} + color = "default" if d["fg"]: # this isn't according to spec as I understand it if d["fg"].isupper(): From 2784b2f781becb8e36531377b851ed6148936e3f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 25 Apr 2022 23:27:06 +0200 Subject: [PATCH 1388/1650] Fix type annotations --- bpython/args.py | 4 ++-- bpython/simpleeval.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 1ab61d260..04cb8fc3e 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -77,9 +77,9 @@ def copyright_banner() -> str: def parse( args: Optional[List[str]], - extras: Options = None, + extras: Optional[Options] = None, ignore_stdin: bool = False, -) -> Tuple: +) -> Tuple[Config, argparse.Namespace, List[str]]: """Receive an argument list - if None, use sys.argv - parse all args and take appropriate action. Also receive optional extra argument: this should be a tuple of (title, description, callback) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 3992a70fc..193a69899 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -28,7 +28,7 @@ import ast import sys import builtins -from typing import Dict, Any +from typing import Dict, Any, Optional from . import line as line_properties from .inspection import getattr_safe @@ -216,7 +216,7 @@ def find_attribute_with_name(node, name): def evaluate_current_expression( - cursor_offset: int, line: str, namespace: Dict[str, Any] = None + cursor_offset: int, line: str, namespace: Optional[Dict[str, Any]] = None ): """ Return evaluated expression to the right of the dot of current attribute. From ee7e295477357abce1f730c6b05e0787a7864313 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 25 Apr 2022 23:53:11 +0200 Subject: [PATCH 1389/1650] Fix type --- bpython/test/test_interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 45ffa66d6..4c18f8fda 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -130,7 +130,7 @@ def gfunc(): self.assertEqual(plain("").join(a), expected) def test_runsource_bytes_over_128_syntax_error_py3(self): - i = interpreter.Interp(encoding=b"latin-1") + i = interpreter.Interp(encoding="latin-1") i.showsyntaxerror = mock.Mock(return_value=None) i.runsource("a = b'\xfe'") From 10c8b37077d825ce8c4f40b2bd2f8bb3647cd2f3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 26 Apr 2022 23:40:48 +0200 Subject: [PATCH 1390/1650] Add type annotations --- bpython/paste.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/bpython/paste.py b/bpython/paste.py index 4d118cfc2..9daca01ad 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -1,6 +1,6 @@ # The MIT License # -# Copyright (c) 2014-2020 Sebastian Ramacher +# Copyright (c) 2014-2022 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -21,13 +21,14 @@ # THE SOFTWARE. import errno -import requests import subprocess -import unicodedata - -from locale import getpreferredencoding +from typing import Tuple from urllib.parse import urljoin, urlparse +import requests +import unicodedata + +from .config import getpreferredencoding from .translations import _ @@ -36,11 +37,11 @@ class PasteFailed(Exception): class PastePinnwand: - def __init__(self, url, expiry): + def __init__(self, url: str, expiry: str) -> None: self.url = url self.expiry = expiry - def paste(self, s): + def paste(self, s: str) -> Tuple[str, str]: """Upload to pastebin via json interface.""" url = urljoin(self.url, "/api/v1/paste") @@ -64,10 +65,10 @@ def paste(self, s): class PasteHelper: - def __init__(self, executable): + def __init__(self, executable: str) -> None: self.executable = executable - def paste(self, s): + def paste(self, s: str) -> Tuple[str, None]: """Call out to helper program for pastebin upload.""" try: @@ -77,6 +78,7 @@ def paste(self, s): stdin=subprocess.PIPE, stdout=subprocess.PIPE, ) + assert helper.stdin is not None helper.stdin.write(s.encode(getpreferredencoding())) output = helper.communicate()[0].decode(getpreferredencoding()) paste_url = output.split()[0] @@ -89,8 +91,8 @@ def paste(self, s): if helper.returncode != 0: raise PasteFailed( _( - "Helper program returned non-zero exit " - "status %d." % (helper.returncode,) + "Helper program returned non-zero exit status %d." + % (helper.returncode,) ) ) From 8aef6a7f2474cf82f015d065dfda7d4b3ecf1427 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 26 Apr 2022 23:40:56 +0200 Subject: [PATCH 1391/1650] Fix exception handling --- bpython/paste.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/paste.py b/bpython/paste.py index 9daca01ad..ebea9e28e 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -54,7 +54,7 @@ def paste(self, s: str) -> Tuple[str, str]: response = requests.post(url, json=payload, verify=True) response.raise_for_status() except requests.exceptions.RequestException as exc: - raise PasteFailed(exc.message) + raise PasteFailed(str(exc)) data = response.json() From 229961238d6cb783067eae3c7bd07f68a4b49927 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 26 Apr 2022 23:50:08 +0200 Subject: [PATCH 1392/1650] Cache encoding --- bpython/paste.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/paste.py b/bpython/paste.py index ebea9e28e..126f5a43a 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -79,8 +79,9 @@ def paste(self, s: str) -> Tuple[str, None]: stdout=subprocess.PIPE, ) assert helper.stdin is not None - helper.stdin.write(s.encode(getpreferredencoding())) - output = helper.communicate()[0].decode(getpreferredencoding()) + encoding = getpreferredencoding() + helper.stdin.write(s.encode(encoding)) + output = helper.communicate()[0].decode(encoding) paste_url = output.split()[0] except OSError as e: if e.errno == errno.ENOENT: From deb64165ad23b99dc1c6f54d63fefed41fb69d48 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 26 Apr 2022 23:50:23 +0200 Subject: [PATCH 1393/1650] Define a protocol for paster implementations --- bpython/paste.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bpython/paste.py b/bpython/paste.py index 126f5a43a..fd140a0ec 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -22,7 +22,7 @@ import errno import subprocess -from typing import Tuple +from typing import Optional, Tuple, Protocol from urllib.parse import urljoin, urlparse import requests @@ -36,6 +36,11 @@ class PasteFailed(Exception): pass +class Paster(Protocol): + def paste(self, s: str) -> Tuple[str, Optional[str]]: + ... + + class PastePinnwand: def __init__(self, url: str, expiry: str) -> None: self.url = url From af5e90ab270956d45b9c9399fc2929ab996d22b6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 27 Apr 2022 00:11:34 +0200 Subject: [PATCH 1394/1650] Fix import of Protocol --- bpython/paste.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/paste.py b/bpython/paste.py index fd140a0ec..ceba59386 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -22,7 +22,7 @@ import errno import subprocess -from typing import Optional, Tuple, Protocol +from typing import Optional, Tuple from urllib.parse import urljoin, urlparse import requests @@ -30,6 +30,7 @@ from .config import getpreferredencoding from .translations import _ +from ._typing_compat import Protocol class PasteFailed(Exception): From 14669c08e28dcb0ad1a186b486e50595bad952f5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 3 May 2022 00:19:15 +0200 Subject: [PATCH 1395/1650] Set up __main__ module (fixes #959, #868) --- bpython/args.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 04cb8fc3e..1212fe3f6 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -243,10 +243,10 @@ def exec_code( raise SystemExit(e.errno) old_argv, sys.argv = sys.argv, args sys.path.insert(0, os.path.abspath(os.path.dirname(args[0]))) - spec = importlib.util.spec_from_loader("__console__", loader=None) + spec = importlib.util.spec_from_loader("__main__", loader=None) assert spec mod = importlib.util.module_from_spec(spec) - sys.modules["__console__"] = mod + sys.modules["__main__"] = mod interpreter.locals.update(mod.__dict__) # type: ignore # TODO use a more specific type that has a .locals attribute interpreter.locals["__file__"] = args[0] # type: ignore # TODO use a more specific type that has a .locals attribute interpreter.runsource(source, args[0], "exec") From 3d3a6633394c69404e53aa6dc1d61b6126489b51 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Sun, 6 Feb 2022 17:22:15 -0600 Subject: [PATCH 1396/1650] Just starting out --- bpython/cli.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bpython/cli.py b/bpython/cli.py index 28cc67c71..03987d072 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -21,6 +21,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # +# mypy: disallow_untyped_defs=True +# mypy: disallow_untyped_calls=True # Modified by Brandon Navra # Notes for Windows From 8a292ed6aafaca98b9532d5f343070956daf89a9 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Tue, 8 Feb 2022 23:08:21 -0600 Subject: [PATCH 1397/1650] Done through line 120 --- bpython/cli.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 03987d072..7194cde3f 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -52,7 +52,7 @@ import struct import sys import time -from typing import Iterator, NoReturn, List +from typing import Iterator, NoReturn, List, MutableMapping, Any import unicodedata from dataclasses import dataclass @@ -66,7 +66,7 @@ from pygments import format from pygments.formatters import TerminalFormatter from pygments.lexers import Python3Lexer -from pygments.token import Token +from pygments.token import Token, _TokenType from .formatter import BPythonFormatter # This for config @@ -100,7 +100,9 @@ class ShowListState: wl: int = 0 -def calculate_screen_lines(tokens, width, cursor=0) -> int: +def calculate_screen_lines( + tokens: MutableMapping[_TokenType, str], width: int, cursor: int = 0 +) -> int: """Given a stream of tokens and a screen width plus an optional initial cursor position, return the amount of needed lines on the screen.""" From 7ae4c794dfd54b803bdf241c963aaa6201d44ab7 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Wed, 9 Feb 2022 20:39:08 -0600 Subject: [PATCH 1398/1650] Not sure about forward_if_not_current() typing. --- bpython/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 7194cde3f..9720e76d8 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -52,7 +52,7 @@ import struct import sys import time -from typing import Iterator, NoReturn, List, MutableMapping, Any +from typing import Iterator, NoReturn, List, MutableMapping, Any, Callable import unicodedata from dataclasses import dataclass @@ -118,9 +118,9 @@ def calculate_screen_lines( return lines -def forward_if_not_current(func): +def forward_if_not_current(func: Callable) -> Callable: @functools.wraps(func) - def newfunc(self, *args, **kwargs): + def newfunc(self, *args: Any, **kwargs: Any) -> Any: dest = self.get_dest() if self is dest: return func(self, *args, **kwargs) From 6fecebdcbe084bf88aa884e6987c327e7694989d Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Thu, 17 Feb 2022 08:55:35 -0600 Subject: [PATCH 1399/1650] Did the forward_if_not_current decorator function. Not 100% sure about the types. --- bpython/cli.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 9720e76d8..2ad20f293 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -52,7 +52,7 @@ import struct import sys import time -from typing import Iterator, NoReturn, List, MutableMapping, Any, Callable +from typing import Iterator, NoReturn, List, MutableMapping, Any, Callable, TypeVar, cast import unicodedata from dataclasses import dataclass @@ -84,6 +84,7 @@ from .pager import page from .args import parse as argsparse +F = TypeVar('F', bound=Callable[..., Any]) # --- module globals --- stdscr = None @@ -118,16 +119,16 @@ def calculate_screen_lines( return lines -def forward_if_not_current(func: Callable) -> Callable: +def forward_if_not_current(func: F) -> F: @functools.wraps(func) - def newfunc(self, *args: Any, **kwargs: Any) -> Any: + def newfunc(self, *args, **kwargs): # type: ignore dest = self.get_dest() if self is dest: return func(self, *args, **kwargs) else: return getattr(self.get_dest(), newfunc.__name__)(*args, **kwargs) - return newfunc + return cast(F, newfunc) class FakeStream: From 9c56f05775cd599279caa340d2a0d66e5ea8daa2 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Fri, 18 Feb 2022 20:12:34 -0600 Subject: [PATCH 1400/1650] Had to punt on get_color() --- bpython/cli.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 2ad20f293..15f93bb8a 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -52,7 +52,7 @@ import struct import sys import time -from typing import Iterator, NoReturn, List, MutableMapping, Any, Callable, TypeVar, cast +from typing import Iterator, NoReturn, List, MutableMapping, Any, Callable, TypeVar, cast, IO, Iterable, Optional import unicodedata from dataclasses import dataclass @@ -70,7 +70,7 @@ from .formatter import BPythonFormatter # This for config -from .config import getpreferredencoding +from .config import getpreferredencoding, Config # This for keys from .keys import cli_key_dispatch as key_dispatch @@ -88,7 +88,7 @@ # --- module globals --- stdscr = None -colors = None +colors: Optional[MutableMapping[str, int]] = None DO_RESIZE = False # --- @@ -135,17 +135,17 @@ class FakeStream: """Provide a fake file object which calls functions on the interface provided.""" - def __init__(self, interface, get_dest): + def __init__(self, interface: 'CLIRepl', get_dest: IO[str]) -> None: self.encoding: str = getpreferredencoding() self.interface = interface self.get_dest = get_dest @forward_if_not_current - def write(self, s) -> None: + def write(self, s: str) -> None: self.interface.write(s) @forward_if_not_current - def writelines(self, l) -> None: + def writelines(self, l: Iterable[str]) -> None: for s in l: self.write(s) @@ -160,7 +160,7 @@ def flush(self) -> None: class FakeStdin: """Provide a fake stdin type for things like raw_input() etc.""" - def __init__(self, interface) -> None: + def __init__(self, interface: 'CLIRepl') -> None: """Take the curses Repl on init and assume it provides a get_key method which, fortunately, it does.""" @@ -171,11 +171,11 @@ def __init__(self, interface) -> None: def __iter__(self) -> Iterator: return iter(self.readlines()) - def flush(self): + def flush(self) -> None: """Flush the internal buffer. This is a no-op. Flushing stdin doesn't make any sense anyway.""" - def write(self, value) -> NoReturn: + def write(self, value: str) -> NoReturn: # XXX IPython expects sys.stdin.write to exist, there will no doubt be # others, so here's a hack to keep them happy raise OSError(errno.EBADF, "sys.stdin is read-only") @@ -183,7 +183,7 @@ def write(self, value) -> NoReturn: def isatty(self) -> bool: return True - def readline(self, size=-1): + def readline(self, size: int = -1) -> str: """I can't think of any reason why anything other than readline would be useful in the context of an interactive interpreter so this is the only one I've done anything with. The others are just there in case @@ -228,7 +228,7 @@ def readline(self, size=-1): return buffer - def read(self, size=None): + def read(self, size: Optional[int] = None) -> str: if size == 0: return "" @@ -243,7 +243,7 @@ def read(self, size=None): return "".join(data) - def readlines(self, size=-1): + def readlines(self, size: int = -1) -> List[str]: return list(iter(self.readline, "")) @@ -260,17 +260,20 @@ def readlines(self, size=-1): # the addstr stuff to a higher level. # - -def get_color(config, name): +# Have to ignore the return type on this one because the colors variable +# is Optional[MutableMapping[str, int]] but for the purposes of this +# function it can't be None +def get_color(config: Config, name: str) -> int: # type: ignore[return] global colors - return colors[config.color_scheme[name].lower()] + if colors: + return colors[config.color_scheme[name].lower()] -def get_colpair(config, name): +def get_colpair(config: Config, name: str) -> int: return curses.color_pair(get_color(config, name) + 1) -def make_colors(config): +def make_colors(config: Config) -> MutableMapping[str, int]: """Init all the colours in curses and bang them into a dictionary""" # blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default: From 3c3a81d0056f272aeeb7c3ec16deb285f1265947 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Fri, 18 Feb 2022 23:58:16 -0600 Subject: [PATCH 1401/1650] Still working my way down. --- bpython/cli.py | 63 +++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 15f93bb8a..812d4b609 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -315,10 +315,10 @@ def make_colors(config: Config) -> MutableMapping[str, int]: class CLIInteraction(repl.Interaction): - def __init__(self, config, statusbar=None): + def __init__(self, config: Config, statusbar: 'Statusbar' = None): super().__init__(config, statusbar) - def confirm(self, q): + def confirm(self, q: str) -> bool: """Ask for yes or no and return boolean""" try: reply = self.statusbar.prompt(q) @@ -327,15 +327,15 @@ def confirm(self, q): return reply.lower() in (_("y"), _("yes")) - def notify(self, s, n=10, wait_for_keypress=False): + def notify(self, s: str, n: int = 10, wait_for_keypress: bool = False) -> None: return self.statusbar.message(s, n) - def file_prompt(self, s): + def file_prompt(self, s: int) -> str: return self.statusbar.prompt(s) class CLIRepl(repl.Repl): - def __init__(self, scr, interp, statusbar, config, idle=None): + def __init__(self, scr: curses.window, interp: repl.Interpreter, statusbar: 'Statusbar', config: Config, idle: None = None): super().__init__(interp, config) self.interp.writetb = self.writetb self.scr = scr @@ -357,10 +357,10 @@ def __init__(self, scr, interp, statusbar, config, idle=None): if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1: config.cli_suggestion_width = 0.8 - def _get_cursor_offset(self): + def _get_cursor_offset(self) -> int: return len(self.s) - self.cpos - def _set_cursor_offset(self, offset): + def _set_cursor_offset(self, offset: int) -> None: self.cpos = len(self.s) - offset cursor_offset = property( @@ -370,7 +370,7 @@ def _set_cursor_offset(self, offset): "The cursor offset from the beginning of the line", ) - def addstr(self, s): + def addstr(self, s: str) -> None: """Add a string to the current input line and figure out where it should go, depending on the cursor position.""" self.rl_history.reset() @@ -382,7 +382,7 @@ def addstr(self, s): self.complete() - def atbol(self): + def atbol(self) -> bool: """Return True or False accordingly if the cursor is at the beginning of the line (whitespace is ignored). This exists so that p_key() knows how to handle the tab key being pressed - if there is nothing but white @@ -391,17 +391,18 @@ def atbol(self): return not self.s.lstrip() - def bs(self, delete_tabs=True): + # This function shouldn't return None because of pos -= self.bs() later on + def bs(self, delete_tabs: bool = True) -> int: # type: ignore[return-value] """Process a backspace""" self.rl_history.reset() y, x = self.scr.getyx() if not self.s: - return + return None # type: ignore[return-value] if x == self.ix and y == self.iy: - return + return None # type: ignore[return-value] n = 1 @@ -422,7 +423,7 @@ def bs(self, delete_tabs=True): return n - def bs_word(self): + def bs_word(self) -> str: self.rl_history.reset() pos = len(self.s) - self.cpos - 1 deleted = [] @@ -437,7 +438,7 @@ def bs_word(self): return "".join(reversed(deleted)) - def check(self): + def check(self) -> None: """Check if paste mode should still be active and, if not, deactivate it and force syntax highlighting.""" @@ -448,14 +449,14 @@ def check(self): self.paste_mode = False self.print_line(self.s) - def clear_current_line(self): + def clear_current_line(self) -> None: """Called when a SyntaxError occurred in the interpreter. It is used to prevent autoindentation from occurring after a traceback.""" repl.Repl.clear_current_line(self) self.s = "" - def clear_wrapped_lines(self): + def clear_wrapped_lines(self) -> None: """Clear the wrapped lines of the current input.""" # curses does not handle this on its own. Sad. height, width = self.scr.getmaxyx() @@ -464,7 +465,7 @@ def clear_wrapped_lines(self): self.scr.move(y, 0) self.scr.clrtoeol() - def complete(self, tab=False): + def complete(self, tab: bool = False) -> None: """Get Autocomplete list and window. Called whenever these should be updated, and called @@ -494,7 +495,7 @@ def complete(self, tab=False): self.scr.redrawwin() self.scr.refresh() - def clrtobol(self): + def clrtobol(self) -> None: """Clear from cursor to beginning of line; usual C-u behaviour""" self.clear_wrapped_lines() @@ -507,10 +508,10 @@ def clrtobol(self): self.scr.redrawwin() self.scr.refresh() - def _get_current_line(self): + def _get_current_line(self) -> str: return self.s - def _set_current_line(self, line): + def _set_current_line(self, line: str) -> None: self.s = line current_line = property( @@ -520,7 +521,7 @@ def _set_current_line(self, line): "The characters of the current line", ) - def cut_to_buffer(self): + def cut_to_buffer(self) -> None: """Clear from cursor to end of line, placing into cut buffer""" self.cut_buffer = self.s[-self.cpos :] self.s = self.s[: -self.cpos] @@ -529,7 +530,7 @@ def cut_to_buffer(self): self.scr.redrawwin() self.scr.refresh() - def delete(self): + def delete(self) -> None: """Process a del""" if not self.s: return @@ -537,7 +538,7 @@ def delete(self): if self.mvc(-1): self.bs(False) - def echo(self, s, redraw=True): + def echo(self, s: str, redraw: bool = True) -> None: """Parse and echo a formatted string with appropriate attributes. It uses the formatting method as defined in formatter.py to parse the strings. It won't update the screen if it's reevaluating the code (as it @@ -571,7 +572,7 @@ def echo(self, s, redraw=True): if redraw and not self.evaluating: self.scr.refresh() - def end(self, refresh=True): + def end(self, refresh: bool = True) -> bool: self.cpos = 0 h, w = gethw() y, x = divmod(len(self.s) + self.ix, w) @@ -582,7 +583,7 @@ def end(self, refresh=True): return True - def hbegin(self): + def hbegin(self) -> None: """Replace the active line with first line in history and increment the index to keep track""" self.cpos = 0 @@ -591,7 +592,7 @@ def hbegin(self): self.s = self.rl_history.first() self.print_line(self.s, clr=True) - def hend(self): + def hend(self) -> None: """Same as hbegin() but, well, forward""" self.cpos = 0 self.clear_wrapped_lines() @@ -599,7 +600,7 @@ def hend(self): self.s = self.rl_history.last() self.print_line(self.s, clr=True) - def back(self): + def back(self) -> None: """Replace the active line with previous line in history and increment the index to keep track""" @@ -609,7 +610,7 @@ def back(self): self.s = self.rl_history.back() self.print_line(self.s, clr=True) - def fwd(self): + def fwd(self) -> None: """Same as back() but, well, forward""" self.cpos = 0 @@ -618,7 +619,7 @@ def fwd(self): self.s = self.rl_history.forward() self.print_line(self.s, clr=True) - def search(self): + def search(self) -> None: """Search with the partial matches from the history object.""" self.cpo = 0 @@ -627,7 +628,7 @@ def search(self): self.s = self.rl_history.back(start=False, search=True) self.print_line(self.s, clr=True) - def get_key(self): + def get_key(self) -> str: key = "" while True: try: @@ -667,7 +668,7 @@ def get_key(self): if self.idle: self.idle(self) - def get_line(self): + def get_line(self) -> Optional[str]: """Get a line of text and return it This function initialises an empty string and gets the curses cursor position on the screen and stores it From f42d1beca1dbae44906943ecf7e299f10e4b11fa Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Sun, 20 Feb 2022 17:26:33 -0600 Subject: [PATCH 1402/1650] Saving progress --- bpython/cli.py | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 812d4b609..cfb1fa254 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -52,7 +52,7 @@ import struct import sys import time -from typing import Iterator, NoReturn, List, MutableMapping, Any, Callable, TypeVar, cast, IO, Iterable, Optional +from typing import Iterator, NoReturn, List, MutableMapping, Any, Callable, TypeVar, cast, IO, Iterable, Optional, Union, Tuple import unicodedata from dataclasses import dataclass @@ -330,7 +330,7 @@ def confirm(self, q: str) -> bool: def notify(self, s: str, n: int = 10, wait_for_keypress: bool = False) -> None: return self.statusbar.message(s, n) - def file_prompt(self, s: int) -> str: + def file_prompt(self, s: str) -> str: return self.statusbar.prompt(s) @@ -668,7 +668,7 @@ def get_key(self) -> str: if self.idle: self.idle(self) - def get_line(self) -> Optional[str]: + def get_line(self) -> str: """Get a line of text and return it This function initialises an empty string and gets the curses cursor position on the screen and stores it @@ -694,14 +694,14 @@ def get_line(self) -> Optional[str]: self.s = self.s[4:] return self.s - def home(self, refresh=True): + def home(self, refresh: bool = True) -> bool: self.scr.move(self.iy, self.ix) self.cpos = len(self.s) if refresh: self.scr.refresh() return True - def lf(self): + def lf(self) -> None: """Process a linefeed character; it only needs to check the cursor position and move appropriately so it doesn't clear the current line after the cursor.""" @@ -713,7 +713,12 @@ def lf(self): self.print_line(self.s, newline=True) self.echo("\n") - def mkargspec(self, topline, in_arg, down): + def mkargspec( + self, + topline: Any, # Named tuples don't seem to play nice with mypy + in_arg: Union[str, int], + down: bool + ) -> int: """This figures out what to do with the argspec and puts it nicely into the list window. It returns the number of lines used to display the argspec. It's also kind of messy due to it having to call so many @@ -817,7 +822,7 @@ def mkargspec(self, topline, in_arg, down): return r - def mvc(self, i, refresh=True): + def mvc(self, i: int, refresh: bool = True) -> bool: """This method moves the cursor relatively from the current position, where: 0 == (right) end of current line @@ -850,7 +855,7 @@ def mvc(self, i, refresh=True): return True - def p_key(self, key): + def p_key(self, key: str) -> Union[None, str, bool]: """Process a keypress""" if key is None: @@ -1044,7 +1049,7 @@ def p_key(self, key): return True - def print_line(self, s, clr=False, newline=False): + def print_line(self, s: Optional[str], clr: bool = False, newline: bool = False) -> None: """Chuck a line of text through the highlighter, move the cursor to the beginning of the line and output it to the screen.""" @@ -1080,7 +1085,10 @@ def print_line(self, s, clr=False, newline=False): self.mvc(1) self.cpos = t - def prompt(self, more): + def prompt( + self, + more: Any # I'm not sure of the type on this one + ) -> None: """Show the appropriate Python prompt""" if not more: self.echo( @@ -1101,7 +1109,7 @@ def prompt(self, more): f"\x01{prompt_more_color}\x03{self.ps2}\x04" ) - def push(self, s, insert_into_history=True): + def push(self, s: str, insert_into_history: bool = True) -> bool: # curses.raw(True) prevents C-c from causing a SIGINT curses.raw(False) try: @@ -1114,7 +1122,7 @@ def push(self, s, insert_into_history=True): finally: curses.raw(True) - def redraw(self): + def redraw(self) -> None: """Redraw the screen using screen_hist""" self.scr.erase() for k, s in enumerate(self.screen_hist): @@ -1130,7 +1138,7 @@ def redraw(self): self.scr.refresh() self.statusbar.refresh() - def repl(self): + def repl(self) -> Tuple: """Initialise the repl and jump into the loop. This method also has to keep a stack of lines entered for the horrible "undo" feature. It also tracks everything that would normally go to stdout in the normal Python @@ -1171,7 +1179,7 @@ def repl(self): self.s = "" return self.exit_value - def reprint_line(self, lineno, tokens): + def reprint_line(self, lineno: int, tokens: MutableMapping[_TokenType, str]) -> None: """Helper function for paren highlighting: Reprint line at offset `lineno` in current input buffer.""" if not self.buffer or lineno == len(self.buffer): @@ -1194,7 +1202,7 @@ def reprint_line(self, lineno, tokens): for string in line.split("\x04"): self.echo(string) - def resize(self): + def resize(self) -> None: """This method exists simply to keep it straight forward when initialising a window and resizing it.""" self.size() @@ -1204,13 +1212,13 @@ def resize(self): self.statusbar.resize(refresh=False) self.redraw() - def getstdout(self): + def getstdout(self) -> str: """This method returns the 'spoofed' stdout buffer, for writing to a file or sending to a pastebin or whatever.""" return self.stdout_hist + "\n" - def reevaluate(self): + def reevaluate(self) -> None: """Clear the buffer, redraw the screen and re-evaluate the history""" self.evaluating = True @@ -1249,7 +1257,7 @@ def reevaluate(self): # map(self.push, self.history) # ^-- That's how simple this method was at first :( - def write(self, s): + def write(self, s: str) -> None: """For overriding stdout defaults""" if "\x04" in s: for block in s.split("\x04"): @@ -1418,7 +1426,7 @@ def suspend(self): curses.endwin() os.kill(os.getpid(), signal.SIGSTOP) - def tab(self, back=False): + def tab(self, back: bool = False) -> bool: """Process the tab key being hit. If there's only whitespace @@ -1498,7 +1506,7 @@ def yank_from_buffer(self): self.addstr(self.cut_buffer) self.print_line(self.s, clr=True) - def send_current_line_to_editor(self): + def send_current_line_to_editor(self) -> str: lines = self.send_to_external_editor(self.s).split("\n") self.s = "" self.print_line(self.s) From b47b91852ebca009a99e1364fbb174b4b1065760 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Wed, 23 Feb 2022 16:21:01 -0600 Subject: [PATCH 1403/1650] Progress --- bpython/cli.py | 124 ++++++++++++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 49 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index cfb1fa254..88c2b026f 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -335,7 +335,14 @@ def file_prompt(self, s: str) -> str: class CLIRepl(repl.Repl): - def __init__(self, scr: curses.window, interp: repl.Interpreter, statusbar: 'Statusbar', config: Config, idle: None = None): + def __init__( + self, + scr: curses.window, + interp: repl.Interpreter, + statusbar: 'Statusbar', + config: Config, + idle: Optional[Callable] = None + ): super().__init__(interp, config) self.interp.writetb = self.writetb self.scr = scr @@ -343,7 +350,7 @@ def __init__(self, scr: curses.window, interp: repl.Interpreter, statusbar: 'Sta self.list_win = newwin(get_colpair(config, "background"), 1, 1, 1, 1) self.cpos = 0 self.do_exit = False - self.exit_value = () + self.exit_value: Tuple[Any, ...] = () self.f_string = "" self.idle = idle self.in_hist = False @@ -1138,7 +1145,7 @@ def redraw(self) -> None: self.scr.refresh() self.statusbar.refresh() - def repl(self) -> Tuple: + def repl(self) -> Tuple[Any, ...]: """Initialise the repl and jump into the loop. This method also has to keep a stack of lines entered for the horrible "undo" feature. It also tracks everything that would normally go to stdout in the normal Python @@ -1277,8 +1284,13 @@ def write(self, s: str) -> None: self.screen_hist.append(s.rstrip()) def show_list( - self, items, arg_pos, topline=None, formatter=None, current_item=None - ): + self, + items: List[str], + arg_pos: Union[str, int], + topline: Any = None, # Named tuples don't play nice with mypy + formatter: Optional[Callable] = None, + current_item: Optional[bool] = None + ) -> None: shared = ShowListState() y, x = self.scr.getyx() h, w = self.scr.getmaxyx() @@ -1290,7 +1302,7 @@ def show_list( max_w = int(w * self.config.cli_suggestion_width) self.list_win.erase() - if items: + if items and formatter: items = [formatter(x) for x in items] if current_item: current_item = formatter(current_item) @@ -1300,7 +1312,7 @@ def show_list( else: height_offset = 0 - def lsize(): + def lsize() -> bool: wl = max(len(i) for i in v_items) + 1 if not wl: wl = 1 @@ -1410,17 +1422,18 @@ def lsize(): self.scr.move(*self.scr.getyx()) self.list_win.refresh() - def size(self): + def size(self) -> None: """Set instance attributes for x and y top left corner coordinates and width and height for the window.""" global stdscr - h, w = stdscr.getmaxyx() - self.y = 0 - self.w = w - self.h = h - 1 - self.x = 0 - - def suspend(self): + if stdscr: + h, w = stdscr.getmaxyx() + self.y: int = 0 + self.w: int = w + self.h: int = h - 1 + self.x: int = 0 + + def suspend(self) -> None: """Suspend the current process for shell job control.""" if platform.system() != "Windows": curses.endwin() @@ -1489,19 +1502,19 @@ def tab(self, back: bool = False) -> bool: self.print_line(self.s, True) return True - def undo(self, n=1): + def undo(self, n: int = 1) -> None: repl.Repl.undo(self, n) # This will unhighlight highlighted parens self.print_line(self.s) - def writetb(self, lines): + def writetb(self, lines: List[str]) -> None: for line in lines: self.write( "\x01{}\x03{}".format(self.config.color_scheme["error"], line) ) - def yank_from_buffer(self): + def yank_from_buffer(self) -> None: """Paste the text from the cut buffer at the current cursor location""" self.addstr(self.cut_buffer) self.print_line(self.s, clr=True) @@ -1569,7 +1582,15 @@ class Statusbar: """ - def __init__(self, scr, pwin, background, config, s=None, c=None): + def __init__( + self, + scr: curses.window, + pwin: curses.window, + background: int, + config: Config, + s: Optional[str] = None, + c: Optional[int] = None + ): """Initialise the statusbar and display the initial text (if any)""" self.size() self.win = newwin(background, self.h, self.w, self.y, self.x) @@ -1581,9 +1602,10 @@ def __init__(self, scr, pwin, background, config, s=None, c=None): self.c = c self.timer = 0 self.pwin = pwin - self.settext(s, c) + if s: + self.settext(s, c) - def size(self): + def size(self) -> None: """Set instance attributes for x and y top left corner coordinates and width and height for the window.""" h, w = gethw() @@ -1592,7 +1614,7 @@ def size(self): self.h = 1 self.x = 0 - def resize(self, refresh=True): + def resize(self, refresh: bool = True) -> None: """This method exists simply to keep it straight forward when initialising a window and resizing it.""" self.size() @@ -1601,12 +1623,12 @@ def resize(self, refresh=True): if refresh: self.refresh() - def refresh(self): + def refresh(self) -> None: """This is here to make sure the status bar text is redraw properly after a resize.""" self.settext(self._s) - def check(self): + def check(self) -> None: """This is the method that should be called every half second or so to see if the status bar needs updating.""" if not self.timer: @@ -1617,13 +1639,13 @@ def check(self): self.settext(self._s) - def message(self, s, n=3): + def message(self, s: str, n: int = 3) -> None: """Display a message for a short n seconds on the statusbar and return it to its original state.""" - self.timer = time.time() + n + self.timer = int(time.time() + n) self.settext(s) - def prompt(self, s=""): + def prompt(self, s: str = "") -> str: """Prompt the user for some input (with the optional prompt 's') and return the input text, then restore the statusbar to its original value.""" @@ -1631,7 +1653,7 @@ def prompt(self, s=""): self.settext(s or "? ", p=True) iy, ix = self.win.getyx() - def bs(s): + def bs(s: str) -> str: y, x = self.win.getyx() if x == ix: return s @@ -1656,14 +1678,14 @@ def bs(s): raise ValueError # literal elif 0 < c < 127: - c = chr(c) - self.win.addstr(c, get_colpair(self.config, "prompt")) - o += c + d = chr(c) + self.win.addstr(d, get_colpair(self.config, "prompt")) + o += d self.settext(self._s) return o - def settext(self, s, c=None, p=False): + def settext(self, s: str, c: Optional[int] = None, p: bool = False) -> None: """Set the text on the status bar to a new permanent value; this is the value that will be set after a prompt or message. c is the optional curses colour pair to use (if not specified the last specified colour @@ -1690,12 +1712,12 @@ def settext(self, s, c=None, p=False): else: self.win.refresh() - def clear(self): + def clear(self) -> None: """Clear the status bar.""" self.win.clear() -def init_wins(scr, config): +def init_wins(scr: curses.window, config: Config) -> Tuple[curses.window, Statusbar]: """Initialise the two windows (the main repl interface and the little status bar at the bottom with some stuff in it)""" # TODO: Document better what stuff is on the status bar. @@ -1705,7 +1727,9 @@ def init_wins(scr, config): main_win = newwin(background, h - 1, w, 0, 0) main_win.scrollok(True) - main_win.keypad(1) + + # I think this is supposed to be True instead of 1? + main_win.keypad(1) # type:ignore[arg-type] # Thanks to Angus Gibson for pointing out this missing line which was causing # problems that needed dirty hackery to fix. :) @@ -1728,18 +1752,18 @@ def init_wins(scr, config): return main_win, statusbar -def sigwinch(unused_scr): +def sigwinch(unused_scr: curses.window) -> None: global DO_RESIZE DO_RESIZE = True -def sigcont(unused_scr): +def sigcont(unused_scr: curses.window) -> None: sigwinch(unused_scr) # Forces the redraw curses.ungetch("\x00") -def gethw(): +def gethw() -> Tuple[int, int]: """I found this code on a usenet post, and snipped out the bit I needed, so thanks to whoever wrote that, sorry I forgot your name, I'm sure you're a great guy. @@ -1757,10 +1781,10 @@ def gethw(): if platform.system() != "Windows": h, w = struct.unpack( - "hhhh", fcntl.ioctl(sys.__stdout__, termios.TIOCGWINSZ, "\000" * 8) + "hhhh", fcntl.ioctl(sys.__stdout__, termios.TIOCGWINSZ, "\000" * 8) # type:ignore[call-overload] )[0:2] else: - from ctypes import windll, create_string_buffer + from ctypes import windll, create_string_buffer # type:ignore[attr-defined] # stdin handle is -10 # stdout handle is -11 @@ -1786,7 +1810,7 @@ def gethw(): ) = struct.unpack("hhhhHhhhhhh", csbi.raw) sizex = right - left + 1 sizey = bottom - top + 1 - else: + elif stdscr: # can't determine actual size - return default values sizex, sizey = stdscr.getmaxyx() @@ -1794,7 +1818,7 @@ def gethw(): return h, w -def idle(caller): +def idle(caller: CLIRepl) -> None: """This is called once every iteration through the getkey() loop (currently in the Repl class, see the get_line() method). The statusbar check needs to go here to take care of timed @@ -1817,7 +1841,7 @@ def idle(caller): do_resize(caller) -def do_resize(caller): +def do_resize(caller: CLIRepl) -> None: """This needs to hack around readline and curses not playing nicely together. See also gethw() above.""" global DO_RESIZE @@ -1844,14 +1868,14 @@ class FakeDict: used as a hacky solution to using a colours dict containing colour codes if colour initialisation fails.""" - def __init__(self, val): + def __init__(self, val: int): self._val = val - def __getitem__(self, k): + def __getitem__(self, k: Any) -> int: return self._val -def newwin(background, *args): +def newwin(background: int, *args: int) -> curses.window: """Wrapper for curses.newwin to automatically set background colour on any newly created window.""" win = curses.newwin(*args) @@ -1859,7 +1883,7 @@ def newwin(background, *args): return win -def curses_wrapper(func, *args, **kwargs): +def curses_wrapper(func: Callable, *args: Any, **kwargs: Any) -> Any: """Like curses.wrapper(), but reuses stdscr when called again.""" global stdscr if stdscr is None: @@ -1867,7 +1891,8 @@ def curses_wrapper(func, *args, **kwargs): try: curses.noecho() curses.cbreak() - stdscr.keypad(1) + # Should this be keypad(True)? + stdscr.keypad(1) # type:ignore[arg-type] try: curses.start_color() @@ -1876,7 +1901,8 @@ def curses_wrapper(func, *args, **kwargs): return func(stdscr, *args, **kwargs) finally: - stdscr.keypad(0) + # Should this be keypad(False)? + stdscr.keypad(0) # type:ignore[arg-type] curses.echo() curses.nocbreak() curses.endwin() From 4e55dde2e2582158cf8d6301b1af292003f3a150 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Sun, 6 Mar 2022 21:06:40 -0600 Subject: [PATCH 1404/1650] Finished squashing the mypy errors, use --follow-imports=error to ignore errors from imports that haven't been typed yet. --- bpython/cli.py | 56 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 88c2b026f..29658f7ee 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -315,23 +315,29 @@ def make_colors(config: Config) -> MutableMapping[str, int]: class CLIInteraction(repl.Interaction): - def __init__(self, config: Config, statusbar: 'Statusbar' = None): + def __init__(self, config: Config, statusbar: Optional['Statusbar'] = None): super().__init__(config, statusbar) def confirm(self, q: str) -> bool: """Ask for yes or no and return boolean""" try: - reply = self.statusbar.prompt(q) + if self.statusbar: + reply = self.statusbar.prompt(q) except ValueError: return False return reply.lower() in (_("y"), _("yes")) def notify(self, s: str, n: int = 10, wait_for_keypress: bool = False) -> None: - return self.statusbar.message(s, n) + if self.statusbar: + self.statusbar.message(s, n) - def file_prompt(self, s: str) -> str: - return self.statusbar.prompt(s) + def file_prompt(self, s: str) -> Optional[str]: + if self.statusbar: + # This thows a mypy error because repl.py isn't typed yet + return self.statusbar.prompt(s) # type:ignore[no-any-return] + else: + return None class CLIRepl(repl.Repl): @@ -970,7 +976,7 @@ def p_key(self, key: str) -> Union[None, str, bool]: elif key in key_dispatch[config.clear_screen_key]: # clear all but current line - self.screen_hist = [self.screen_hist[-1]] + self.screen_hist: List = [self.screen_hist[-1]] self.highlighted_paren = None self.redraw() return "" @@ -1120,7 +1126,8 @@ def push(self, s: str, insert_into_history: bool = True) -> bool: # curses.raw(True) prevents C-c from causing a SIGINT curses.raw(False) try: - return repl.Repl.push(self, s, insert_into_history) + x: bool = repl.Repl.push(self, s, insert_into_history) + return x except SystemExit as e: # Avoid a traceback on e.g. quit() self.do_exit = True @@ -1231,7 +1238,7 @@ def reevaluate(self) -> None: self.evaluating = True self.stdout_hist = "" self.f_string = "" - self.buffer = [] + self.buffer: List[str] = [] self.scr.erase() self.screen_hist = [] # Set cursor position to -1 to prevent paren matching @@ -1593,7 +1600,7 @@ def __init__( ): """Initialise the statusbar and display the initial text (if any)""" self.size() - self.win = newwin(background, self.h, self.w, self.y, self.x) + self.win: curses.window = newwin(background, self.h, self.w, self.y, self.x) self.config = config @@ -1908,7 +1915,14 @@ def curses_wrapper(func: Callable, *args: Any, **kwargs: Any) -> Any: curses.endwin() -def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): +def main_curses( + scr: curses.window, + args: List[str], + config: Config, + interactive: bool = True, + locals_: Optional[MutableMapping[str, str]] = None, + banner: Optional[str] = None +) -> Tuple[Tuple[Any, ...], str]: """main function for the curses convenience wrapper Initialise the two main objects: the interpreter @@ -1941,7 +1955,9 @@ def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): curses.use_default_colors() cols = make_colors(config) except curses.error: - cols = FakeDict(-1) + # Not sure what to do with the types here... + # FakeDict acts as a dictionary, but isn't actually a dictionary + cols = FakeDict(-1) # type:ignore[assignment] # FIXME: Gargh, bad design results in using globals without a refactor :( colors = cols @@ -1956,12 +1972,13 @@ def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): clirepl = CLIRepl(main_win, interpreter, statusbar, config, idle) clirepl._C = cols - sys.stdin = FakeStdin(clirepl) - sys.stdout = FakeStream(clirepl, lambda: sys.stdout) - sys.stderr = FakeStream(clirepl, lambda: sys.stderr) + # Not sure how to type these Fake types + sys.stdin = FakeStdin(clirepl) # type:ignore[assignment] + sys.stdout = FakeStream(clirepl, lambda: sys.stdout) # type:ignore + sys.stderr = FakeStream(clirepl, lambda: sys.stderr) # type:ignore if args: - exit_value = () + exit_value: Tuple[Any, ...] = () try: bpargs.exec_code(interpreter, args) except SystemExit as e: @@ -1995,7 +2012,8 @@ def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): exit_value = clirepl.repl() if hasattr(sys, "exitfunc"): - sys.exitfunc() + # Seems like the if statment should satisfy mypy, but it doesn't + sys.exitfunc() # type:ignore[attr-defined] delattr(sys, "exitfunc") main_win.erase() @@ -2012,7 +2030,11 @@ def main_curses(scr, args, config, interactive=True, locals_=None, banner=None): return (exit_value, clirepl.getstdout()) -def main(args=None, locals_=None, banner=None): +def main( + args: Optional[List[str]] = None, + locals_: Optional[MutableMapping[str, str]] = None, + banner: Optional[str] = None +) -> Any: translations.init() config, options, exec_args = argsparse(args) From 136631d4547effee53b7eba8aaeb75e904598497 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Sun, 6 Mar 2022 21:13:23 -0600 Subject: [PATCH 1405/1650] Formatted with Black --- bpython/cli.py | 127 ++++++++++++++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 50 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 29658f7ee..ed885669b 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -52,7 +52,21 @@ import struct import sys import time -from typing import Iterator, NoReturn, List, MutableMapping, Any, Callable, TypeVar, cast, IO, Iterable, Optional, Union, Tuple +from typing import ( + Iterator, + NoReturn, + List, + MutableMapping, + Any, + Callable, + TypeVar, + cast, + IO, + Iterable, + Optional, + Union, + Tuple, +) import unicodedata from dataclasses import dataclass @@ -84,7 +98,7 @@ from .pager import page from .args import parse as argsparse -F = TypeVar('F', bound=Callable[..., Any]) +F = TypeVar("F", bound=Callable[..., Any]) # --- module globals --- stdscr = None @@ -135,7 +149,7 @@ class FakeStream: """Provide a fake file object which calls functions on the interface provided.""" - def __init__(self, interface: 'CLIRepl', get_dest: IO[str]) -> None: + def __init__(self, interface: "CLIRepl", get_dest: IO[str]) -> None: self.encoding: str = getpreferredencoding() self.interface = interface self.get_dest = get_dest @@ -160,7 +174,7 @@ def flush(self) -> None: class FakeStdin: """Provide a fake stdin type for things like raw_input() etc.""" - def __init__(self, interface: 'CLIRepl') -> None: + def __init__(self, interface: "CLIRepl") -> None: """Take the curses Repl on init and assume it provides a get_key method which, fortunately, it does.""" @@ -263,7 +277,7 @@ def readlines(self, size: int = -1) -> List[str]: # Have to ignore the return type on this one because the colors variable # is Optional[MutableMapping[str, int]] but for the purposes of this # function it can't be None -def get_color(config: Config, name: str) -> int: # type: ignore[return] +def get_color(config: Config, name: str) -> int: # type: ignore[return] global colors if colors: return colors[config.color_scheme[name].lower()] @@ -315,7 +329,7 @@ def make_colors(config: Config) -> MutableMapping[str, int]: class CLIInteraction(repl.Interaction): - def __init__(self, config: Config, statusbar: Optional['Statusbar'] = None): + def __init__(self, config: Config, statusbar: Optional["Statusbar"] = None): super().__init__(config, statusbar) def confirm(self, q: str) -> bool: @@ -328,7 +342,9 @@ def confirm(self, q: str) -> bool: return reply.lower() in (_("y"), _("yes")) - def notify(self, s: str, n: int = 10, wait_for_keypress: bool = False) -> None: + def notify( + self, s: str, n: int = 10, wait_for_keypress: bool = False + ) -> None: if self.statusbar: self.statusbar.message(s, n) @@ -342,12 +358,12 @@ def file_prompt(self, s: str) -> Optional[str]: class CLIRepl(repl.Repl): def __init__( - self, - scr: curses.window, - interp: repl.Interpreter, - statusbar: 'Statusbar', - config: Config, - idle: Optional[Callable] = None + self, + scr: curses.window, + interp: repl.Interpreter, + statusbar: "Statusbar", + config: Config, + idle: Optional[Callable] = None, ): super().__init__(interp, config) self.interp.writetb = self.writetb @@ -412,10 +428,10 @@ def bs(self, delete_tabs: bool = True) -> int: # type: ignore[return-value] y, x = self.scr.getyx() if not self.s: - return None # type: ignore[return-value] + return None # type: ignore[return-value] if x == self.ix and y == self.iy: - return None # type: ignore[return-value] + return None # type: ignore[return-value] n = 1 @@ -727,10 +743,10 @@ def lf(self) -> None: self.echo("\n") def mkargspec( - self, - topline: Any, # Named tuples don't seem to play nice with mypy - in_arg: Union[str, int], - down: bool + self, + topline: Any, # Named tuples don't seem to play nice with mypy + in_arg: Union[str, int], + down: bool, ) -> int: """This figures out what to do with the argspec and puts it nicely into the list window. It returns the number of lines used to display the @@ -1062,7 +1078,9 @@ def p_key(self, key: str) -> Union[None, str, bool]: return True - def print_line(self, s: Optional[str], clr: bool = False, newline: bool = False) -> None: + def print_line( + self, s: Optional[str], clr: bool = False, newline: bool = False + ) -> None: """Chuck a line of text through the highlighter, move the cursor to the beginning of the line and output it to the screen.""" @@ -1098,10 +1116,7 @@ def print_line(self, s: Optional[str], clr: bool = False, newline: bool = False) self.mvc(1) self.cpos = t - def prompt( - self, - more: Any # I'm not sure of the type on this one - ) -> None: + def prompt(self, more: Any) -> None: # I'm not sure of the type on this one """Show the appropriate Python prompt""" if not more: self.echo( @@ -1193,7 +1208,9 @@ def repl(self) -> Tuple[Any, ...]: self.s = "" return self.exit_value - def reprint_line(self, lineno: int, tokens: MutableMapping[_TokenType, str]) -> None: + def reprint_line( + self, lineno: int, tokens: MutableMapping[_TokenType, str] + ) -> None: """Helper function for paren highlighting: Reprint line at offset `lineno` in current input buffer.""" if not self.buffer or lineno == len(self.buffer): @@ -1291,12 +1308,12 @@ def write(self, s: str) -> None: self.screen_hist.append(s.rstrip()) def show_list( - self, - items: List[str], - arg_pos: Union[str, int], - topline: Any = None, # Named tuples don't play nice with mypy - formatter: Optional[Callable] = None, - current_item: Optional[bool] = None + self, + items: List[str], + arg_pos: Union[str, int], + topline: Any = None, # Named tuples don't play nice with mypy + formatter: Optional[Callable] = None, + current_item: Optional[bool] = None, ) -> None: shared = ShowListState() y, x = self.scr.getyx() @@ -1591,16 +1608,18 @@ class Statusbar: def __init__( self, - scr: curses.window, - pwin: curses.window, - background: int, - config: Config, - s: Optional[str] = None, - c: Optional[int] = None + scr: curses.window, + pwin: curses.window, + background: int, + config: Config, + s: Optional[str] = None, + c: Optional[int] = None, ): """Initialise the statusbar and display the initial text (if any)""" self.size() - self.win: curses.window = newwin(background, self.h, self.w, self.y, self.x) + self.win: curses.window = newwin( + background, self.h, self.w, self.y, self.x + ) self.config = config @@ -1724,7 +1743,9 @@ def clear(self) -> None: self.win.clear() -def init_wins(scr: curses.window, config: Config) -> Tuple[curses.window, Statusbar]: +def init_wins( + scr: curses.window, config: Config +) -> Tuple[curses.window, Statusbar]: """Initialise the two windows (the main repl interface and the little status bar at the bottom with some stuff in it)""" # TODO: Document better what stuff is on the status bar. @@ -1788,10 +1809,16 @@ def gethw() -> Tuple[int, int]: if platform.system() != "Windows": h, w = struct.unpack( - "hhhh", fcntl.ioctl(sys.__stdout__, termios.TIOCGWINSZ, "\000" * 8) # type:ignore[call-overload] + "hhhh", + fcntl.ioctl( + sys.__stdout__, termios.TIOCGWINSZ, "\000" * 8 + ), # type:ignore[call-overload] )[0:2] else: - from ctypes import windll, create_string_buffer # type:ignore[attr-defined] + from ctypes import ( + windll, + create_string_buffer, + ) # type:ignore[attr-defined] # stdin handle is -10 # stdout handle is -11 @@ -1916,12 +1943,12 @@ def curses_wrapper(func: Callable, *args: Any, **kwargs: Any) -> Any: def main_curses( - scr: curses.window, - args: List[str], - config: Config, - interactive: bool = True, - locals_: Optional[MutableMapping[str, str]] = None, - banner: Optional[str] = None + scr: curses.window, + args: List[str], + config: Config, + interactive: bool = True, + locals_: Optional[MutableMapping[str, str]] = None, + banner: Optional[str] = None, ) -> Tuple[Tuple[Any, ...], str]: """main function for the curses convenience wrapper @@ -2031,9 +2058,9 @@ def main_curses( def main( - args: Optional[List[str]] = None, - locals_: Optional[MutableMapping[str, str]] = None, - banner: Optional[str] = None + args: Optional[List[str]] = None, + locals_: Optional[MutableMapping[str, str]] = None, + banner: Optional[str] = None, ) -> Any: translations.init() From 2ae5c4510ddfd7e6852a429c33fa149e38957754 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Wed, 16 Mar 2022 21:54:27 -0500 Subject: [PATCH 1406/1650] Done with cli.py, working on repl.py --- bpython/cli.py | 44 +++++++++++++++++++++-------- bpython/repl.py | 74 +++++++++++++++++++++++++------------------------ 2 files changed, 70 insertions(+), 48 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index ed885669b..c88dac970 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -66,6 +66,8 @@ Optional, Union, Tuple, + Collection, + Dict ) import unicodedata from dataclasses import dataclass @@ -287,7 +289,7 @@ def get_colpair(config: Config, name: str) -> int: return curses.color_pair(get_color(config, name) + 1) -def make_colors(config: Config) -> MutableMapping[str, int]: +def make_colors(config: Config) -> Dict[str, int]: """Init all the colours in curses and bang them into a dictionary""" # blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default: @@ -366,8 +368,10 @@ def __init__( idle: Optional[Callable] = None, ): super().__init__(interp, config) - self.interp.writetb = self.writetb - self.scr = scr + # mypy doesn't quite understand the difference between a class variable with a callable type and a method. + # https://github.com/python/mypy/issues/2427 + self.interp.writetb = self.writetb # type:ignore[assignment] + self.scr: curses.window = scr self.stdout_hist = "" # native str (bytes in Py2, unicode in Py3) self.list_win = newwin(get_colpair(config, "background"), 1, 1, 1, 1) self.cpos = 0 @@ -382,6 +386,10 @@ def __init__( self.statusbar = statusbar self.formatter = BPythonFormatter(config.color_scheme) self.interact = CLIInteraction(self.config, statusbar=self.statusbar) + self.ix: int + self.iy: int + self.arg_pos: Union[str, int, None] + self.prev_block_finished: int if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1: config.cli_suggestion_width = 0.8 @@ -505,13 +513,18 @@ def complete(self, tab: bool = False) -> None: return list_win_visible = repl.Repl.complete(self, tab) + + f = None + if self.matches_iter.completer: + f = self.matches_iter.completer.format + if list_win_visible: try: self.show_list( self.matches_iter.matches, self.arg_pos, topline=self.funcprops, - formatter=self.matches_iter.completer.format, + formatter=f, ) except curses.error: # XXX: This is a massive hack, it will go away when I get @@ -745,7 +758,7 @@ def lf(self) -> None: def mkargspec( self, topline: Any, # Named tuples don't seem to play nice with mypy - in_arg: Union[str, int], + in_arg: Union[str, int, None], down: bool, ) -> int: """This figures out what to do with the argspec and puts it nicely into @@ -1310,11 +1323,12 @@ def write(self, s: str) -> None: def show_list( self, items: List[str], - arg_pos: Union[str, int], + arg_pos: Union[str, int, None], topline: Any = None, # Named tuples don't play nice with mypy formatter: Optional[Callable] = None, - current_item: Optional[bool] = None, + current_item: Optional[str] = None, ) -> None: + v_items: Collection shared = ShowListState() y, x = self.scr.getyx() h, w = self.scr.getmaxyx() @@ -1475,6 +1489,10 @@ def tab(self, back: bool = False) -> bool: and don't indent if there are only whitespace in the line. """ + f = None + if self.matches_iter.completer: + f = self.matches_iter.completer.format + # 1. check if we should add a tab character if self.atbol() and not back: x_pos = len(self.s) - self.cpos @@ -1505,15 +1523,16 @@ def tab(self, back: bool = False) -> bool: # 4. swap current word for a match list item elif self.matches_iter.matches: - current_match = ( - back and self.matches_iter.previous() or next(self.matches_iter) + n: str = next(self.matches_iter) + current_match: Optional[str] = ( + back and self.matches_iter.previous() or n ) try: self.show_list( self.matches_iter.matches, self.arg_pos, topline=self.funcprops, - formatter=self.matches_iter.completer.format, + formatter=f, current_item=current_match, ) except curses.error: @@ -1815,10 +1834,11 @@ def gethw() -> Tuple[int, int]: ), # type:ignore[call-overload] )[0:2] else: - from ctypes import ( + # Ignoring mypy's windll error because it's Windows-specific + from ctypes import ( # type:ignore[attr-defined] windll, create_string_buffer, - ) # type:ignore[attr-defined] + ) # stdin handle is -10 # stdout handle is -11 diff --git a/bpython/repl.py b/bpython/repl.py index 4ee4eebae..f2e599edd 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -37,11 +37,11 @@ from itertools import takewhile from pathlib import Path from types import ModuleType, TracebackType -from typing import cast, List, Tuple, Any, Optional, Type +from typing import cast, List, Tuple, Any, Optional, Type, Union, MutableMapping, Callable, Dict from ._typing_compat import Literal from pygments.lexers import Python3Lexer -from pygments.token import Token +from pygments.token import Token, _TokenType have_pyperclip = True try: @@ -50,7 +50,8 @@ have_pyperclip = False from . import autocomplete, inspection, simpleeval -from .config import getpreferredencoding +from .cli import Statusbar +from .config import getpreferredencoding, Config from .formatter import Parenthesis from .history import History from .lazyre import LazyReCompile @@ -92,7 +93,7 @@ class Interpreter(code.InteractiveInterpreter): bpython_input_re = LazyReCompile(r"") - def __init__(self, locals=None, encoding=None): + def __init__(self, locals: Optional[MutableMapping[str, str]] = None, encoding: Optional[str] = None): """Constructor. The optional 'locals' argument specifies the dictionary in which code @@ -114,7 +115,7 @@ def __init__(self, locals=None, encoding=None): """ self.encoding = encoding or getpreferredencoding() - self.syntaxerror_callback = None + self.syntaxerror_callback: Optional[Callable] = None if locals is None: # instead of messing with sys.modules, we should modify sys.modules @@ -349,7 +350,7 @@ def clear(self) -> None: class Interaction: - def __init__(self, config, statusbar=None): + def __init__(self, config: Config, statusbar: Optional[Statusbar] = None): self.config = config if statusbar: @@ -402,7 +403,7 @@ class Repl: XXX Subclasses should implement echo, current_line, cw """ - def __init__(self, interp, config): + def __init__(self, interp: Interpreter, config: Config): """Initialise the repl. interp is a Python code.InteractiveInterpreter instance @@ -412,7 +413,7 @@ def __init__(self, interp, config): self.config = config self.cut_buffer = "" - self.buffer = [] + self.buffer: List[str] = [] self.interp = interp self.interp.syntaxerror_callback = self.clear_current_line self.match = False @@ -421,17 +422,17 @@ def __init__(self, interp, config): ) # all input and output, stored as old style format strings # (\x01, \x02, ...) for cli.py - self.screen_hist = [] - self.history = [] # commands executed since beginning of session - self.redo_stack = [] + self.screen_hist: List[str] = [] + self.history: List[str] = [] # commands executed since beginning of session + self.redo_stack: List[str] = [] self.evaluating = False self.matches_iter = MatchesIterator() self.funcprops = None - self.arg_pos = None + self.arg_pos: Union[str, int, None] = None self.current_func = None self.highlighted_paren = None - self._C = {} - self.prev_block_finished = 0 + self._C: Dict[str, int] = {} + self.prev_block_finished: int = 0 self.interact = Interaction(self.config) # previous pastebin content to prevent duplicate pastes, filled on call # to repl.pastebin @@ -441,11 +442,12 @@ def __init__(self, interp, config): # Necessary to fix mercurial.ui.ui expecting sys.stderr to have this # attribute self.closed = False + self.paster: Union[PasteHelper, PastePinnwand] if self.config.hist_file.exists(): try: self.rl_history.load( - self.config.hist_file, getpreferredencoding() or "ascii" + str(self.config.hist_file), getpreferredencoding() or "ascii" ) except OSError: pass @@ -472,7 +474,7 @@ def ps1(self) -> str: def ps2(self) -> str: return cast(str, getattr(sys, "ps2", "... ")) - def startup(self): + def startup(self) -> None: """ Execute PYTHONSTARTUP file if it exits. Call this after front end-specific initialisation. @@ -642,7 +644,7 @@ def get_args(self): self.arg_pos = None return False - def get_source_of_current_name(self): + def get_source_of_current_name(self) -> str: """Return the unicode source code of the object which is bound to the current name in the current input line. Throw `SourceNotFound` if the source cannot be found.""" @@ -692,7 +694,7 @@ def set_docstring(self): # If exactly one match that is equal to current line, clear matches # If example one match and tab=True, then choose that and clear matches - def complete(self, tab=False): + def complete(self, tab: bool = False) -> Optional[bool]: """Construct a full list of possible completions and display them in a window. Also check if there's an available argspec (via the inspect module) and bang that on top of the completions too. @@ -743,7 +745,7 @@ def complete(self, tab=False): else: return tab or completer.shown_before_tab - def format_docstring(self, docstring, width, height): + def format_docstring(self, docstring: str, width: int, height: int) -> str: """Take a string and try to format it into a sane list of strings to be put into the suggestion box.""" @@ -763,7 +765,7 @@ def format_docstring(self, docstring, width, height): out[-1] = out[-1].rstrip() return out - def next_indentation(self): + def next_indentation(self) -> int: """Return the indentation of the next line based on the current input buffer.""" if self.buffer: @@ -804,7 +806,7 @@ def process(): return "\n".join(process()) - def write2file(self): + def write2file(self) -> None: """Prompt for a filename and write the current contents of the stdout buffer to disk.""" @@ -851,7 +853,7 @@ def write2file(self): else: self.interact.notify(_("Saved to %s.") % (fn,)) - def copy2clipboard(self): + def copy2clipboard(self) -> None: """Copy current content to clipboard.""" if not have_pyperclip: @@ -866,7 +868,7 @@ def copy2clipboard(self): else: self.interact.notify(_("Copied content to clipboard.")) - def pastebin(self, s=None): + def pastebin(self, s=None) -> Optional[str]: """Upload to a pastebin and display the URL in the status bar.""" if s is None: @@ -879,7 +881,7 @@ def pastebin(self, s=None): else: return self.do_pastebin(s) - def do_pastebin(self, s): + def do_pastebin(self, s) -> Optional[str]: """Actually perform the upload.""" if s == self.prev_pastebin_content: self.interact.notify( @@ -911,7 +913,7 @@ def do_pastebin(self, s): return paste_url - def push(self, s, insert_into_history=True): + def push(self, s, insert_into_history=True) -> bool: """Push a line of code onto the buffer so it can process it all at once when a code block ends""" # This push method is used by cli and urwid, but not curtsies @@ -936,7 +938,7 @@ def insert_into_history(self, s): except RuntimeError as e: self.interact.notify(f"{e}") - def prompt_undo(self): + def prompt_undo(self) -> int: """Returns how many lines to undo, 0 means don't undo""" if ( self.config.single_undo_time < 0 @@ -944,14 +946,14 @@ def prompt_undo(self): ): return 1 est = self.interp.timer.estimate() - n = self.interact.file_prompt( + m = self.interact.file_prompt( _("Undo how many lines? (Undo will take up to ~%.1f seconds) [1]") % (est,) ) try: - if n == "": - n = "1" - n = int(n) + if m == "": + m = "1" + n = int(m) except ValueError: self.interact.notify(_("Undo canceled"), 0.1) return 0 @@ -968,7 +970,7 @@ def prompt_undo(self): self.interact.notify(message % (n, est), 0.1) return n - def undo(self, n=1): + def undo(self, n: int = 1) -> None: """Go back in the undo history n steps and call reevaluate() Note that in the program this is called "Rewind" because I want it to be clear that this is by no means a true undo @@ -992,7 +994,7 @@ def undo(self, n=1): self.rl_history.entries = entries - def flush(self): + def flush(self) -> None: """Olivier Grisel brought it to my attention that the logging module tries to call this method, since it makes assumptions about stdout that may not necessarily be true. The docs for @@ -1009,7 +1011,7 @@ def flush(self): def close(self): """See the flush() method docstring.""" - def tokenize(self, s, newline=False): + def tokenize(self, s, newline=False) -> List[Tuple[_TokenType, str]]: """Tokenizes a line of code, returning pygments tokens with side effects/impurities: - reads self.cpos to see what parens should be highlighted @@ -1107,11 +1109,11 @@ def tokenize(self, s, newline=False): return list() return line_tokens - def clear_current_line(self): + def clear_current_line(self) -> None: """This is used as the exception callback for the Interpreter instance. It prevents autoindentation from occurring after a traceback.""" - def send_to_external_editor(self, text): + def send_to_external_editor(self, text: str) -> str: """Returns modified text from an editor, or the original text if editor exited with non-zero""" @@ -1169,7 +1171,7 @@ def edit_config(self): self.interact.notify(_("Error editing config file: %s") % e) -def next_indentation(line, tab_length): +def next_indentation(line, tab_length) -> int: """Given a code line, return the indentation of the next line.""" line = line.expandtabs(tab_length) indentation = (len(line) - len(line.lstrip(" "))) // tab_length From c2f1c3919606e80d5376865a4cb1beefe69d0080 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Wed, 23 Mar 2022 20:49:33 -0500 Subject: [PATCH 1407/1650] Mostly done, but have some questions about the missing attributes in repl.py --- bpython/cli.py | 3 +-- bpython/repl.py | 60 +++++++++++++++++++++++++++---------------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index c88dac970..f129ba761 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -352,8 +352,7 @@ def notify( def file_prompt(self, s: str) -> Optional[str]: if self.statusbar: - # This thows a mypy error because repl.py isn't typed yet - return self.statusbar.prompt(s) # type:ignore[no-any-return] + return self.statusbar.prompt(s) else: return None diff --git a/bpython/repl.py b/bpython/repl.py index f2e599edd..f13d487a6 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -430,7 +430,7 @@ def __init__(self, interp: Interpreter, config: Config): self.funcprops = None self.arg_pos: Union[str, int, None] = None self.current_func = None - self.highlighted_paren = None + self.highlighted_paren: Optional[Tuple[Any, List[Tuple[_TokenType, str]]]] = None self._C: Dict[str, int] = {} self.prev_block_finished: int = 0 self.interact = Interaction(self.config) @@ -649,7 +649,7 @@ def get_source_of_current_name(self) -> str: current name in the current input line. Throw `SourceNotFound` if the source cannot be found.""" - obj = self.current_func + obj: Optional[Callable] = self.current_func try: if obj is None: line = self.current_line @@ -657,7 +657,8 @@ def get_source_of_current_name(self) -> str: raise SourceNotFound(_("Nothing to get source of")) if inspection.is_eval_safe_name(line): obj = self.get_object(line) - return inspect.getsource(obj) + # Ignoring the next mypy error because we want this to fail if obj is None + return inspect.getsource(obj) # type:ignore[arg-type] except (AttributeError, NameError) as e: msg = _("Cannot get source: %s") % (e,) except OSError as e: @@ -724,28 +725,31 @@ def complete(self, tab: bool = False) -> Optional[bool]: self.matches_iter.clear() return bool(self.funcprops) - self.matches_iter.update( - self.cursor_offset, self.current_line, matches, completer - ) + if completer: + self.matches_iter.update( + self.cursor_offset, self.current_line, matches, completer + ) - if len(matches) == 1: - if tab: - # if this complete is being run for a tab key press, substitute - # common sequence - ( - self._cursor_offset, - self._current_line, - ) = self.matches_iter.substitute_cseq() - return Repl.complete(self) # again for - elif self.matches_iter.current_word == matches[0]: - self.matches_iter.clear() - return False - return completer.shown_before_tab + if len(matches) == 1: + if tab: + # if this complete is being run for a tab key press, substitute + # common sequence + ( + self._cursor_offset, + self._current_line, + ) = self.matches_iter.substitute_cseq() + return Repl.complete(self) # again for + elif self.matches_iter.current_word == matches[0]: + self.matches_iter.clear() + return False + return completer.shown_before_tab + else: + return tab or completer.shown_before_tab else: - return tab or completer.shown_before_tab + return False - def format_docstring(self, docstring: str, width: int, height: int) -> str: + def format_docstring(self, docstring: str, width: int, height: int) -> List[str]: """Take a string and try to format it into a sane list of strings to be put into the suggestion box.""" @@ -878,11 +882,13 @@ def pastebin(self, s=None) -> Optional[str]: _("Pastebin buffer? (y/N) ") ): self.interact.notify(_("Pastebin aborted.")) + return None else: return self.do_pastebin(s) def do_pastebin(self, s) -> Optional[str]: """Actually perform the upload.""" + paste_url: str if s == self.prev_pastebin_content: self.interact.notify( _("Duplicate pastebin. Previous URL: %s. " "Removal URL: %s") @@ -896,7 +902,7 @@ def do_pastebin(self, s) -> Optional[str]: paste_url, removal_url = self.paster.paste(s) except PasteFailed as e: self.interact.notify(_("Upload failed: %s") % e) - return + return None self.prev_pastebin_content = s self.prev_pastebin_url = paste_url @@ -923,7 +929,7 @@ def push(self, s, insert_into_history=True) -> bool: if insert_into_history: self.insert_into_history(s) - more = self.interp.runsource("\n".join(self.buffer)) + more: bool = self.interp.runsource("\n".join(self.buffer)) if not more: self.buffer = [] @@ -1028,7 +1034,7 @@ def tokenize(self, s, newline=False) -> List[Tuple[_TokenType, str]]: cursor = len(source) - self.cpos if self.cpos: cursor += 1 - stack = list() + stack: List[Any] = list() all_tokens = list(Python3Lexer().get_tokens(source)) # Unfortunately, Pygments adds a trailing newline and strings with # no size, so strip them @@ -1037,8 +1043,8 @@ def tokenize(self, s, newline=False) -> List[Tuple[_TokenType, str]]: all_tokens[-1] = (all_tokens[-1][0], all_tokens[-1][1].rstrip("\n")) line = pos = 0 parens = dict(zip("{([", "})]")) - line_tokens = list() - saved_tokens = list() + line_tokens: List[Tuple[_TokenType, str]] = list() + saved_tokens: List[Tuple[_TokenType, str]] = list() search_for_paren = True for (token, value) in split_lines(all_tokens): pos += len(value) @@ -1174,7 +1180,7 @@ def edit_config(self): def next_indentation(line, tab_length) -> int: """Given a code line, return the indentation of the next line.""" line = line.expandtabs(tab_length) - indentation = (len(line) - len(line.lstrip(" "))) // tab_length + indentation: int = (len(line) - len(line.lstrip(" "))) // tab_length if line.rstrip().endswith(":"): indentation += 1 elif indentation >= 1: From 2de72b30131ced90bc11e818fb20725632969269 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Wed, 23 Mar 2022 21:02:28 -0500 Subject: [PATCH 1408/1650] Ran black and codespell --- bpython/cli.py | 4 ++-- bpython/repl.py | 34 ++++++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index f129ba761..d081e9405 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -67,7 +67,7 @@ Union, Tuple, Collection, - Dict + Dict, ) import unicodedata from dataclasses import dataclass @@ -2058,7 +2058,7 @@ def main_curses( exit_value = clirepl.repl() if hasattr(sys, "exitfunc"): - # Seems like the if statment should satisfy mypy, but it doesn't + # Seems like the if statement should satisfy mypy, but it doesn't sys.exitfunc() # type:ignore[attr-defined] delattr(sys, "exitfunc") diff --git a/bpython/repl.py b/bpython/repl.py index f13d487a6..31294bde7 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -37,7 +37,18 @@ from itertools import takewhile from pathlib import Path from types import ModuleType, TracebackType -from typing import cast, List, Tuple, Any, Optional, Type, Union, MutableMapping, Callable, Dict +from typing import ( + cast, + List, + Tuple, + Any, + Optional, + Type, + Union, + MutableMapping, + Callable, + Dict, +) from ._typing_compat import Literal from pygments.lexers import Python3Lexer @@ -93,7 +104,11 @@ class Interpreter(code.InteractiveInterpreter): bpython_input_re = LazyReCompile(r"") - def __init__(self, locals: Optional[MutableMapping[str, str]] = None, encoding: Optional[str] = None): + def __init__( + self, + locals: Optional[MutableMapping[str, str]] = None, + encoding: Optional[str] = None, + ): """Constructor. The optional 'locals' argument specifies the dictionary in which code @@ -423,14 +438,18 @@ def __init__(self, interp: Interpreter, config: Config): # all input and output, stored as old style format strings # (\x01, \x02, ...) for cli.py self.screen_hist: List[str] = [] - self.history: List[str] = [] # commands executed since beginning of session + self.history: List[ + str + ] = [] # commands executed since beginning of session self.redo_stack: List[str] = [] self.evaluating = False self.matches_iter = MatchesIterator() self.funcprops = None self.arg_pos: Union[str, int, None] = None self.current_func = None - self.highlighted_paren: Optional[Tuple[Any, List[Tuple[_TokenType, str]]]] = None + self.highlighted_paren: Optional[ + Tuple[Any, List[Tuple[_TokenType, str]]] + ] = None self._C: Dict[str, int] = {} self.prev_block_finished: int = 0 self.interact = Interaction(self.config) @@ -447,7 +466,8 @@ def __init__(self, interp: Interpreter, config: Config): if self.config.hist_file.exists(): try: self.rl_history.load( - str(self.config.hist_file), getpreferredencoding() or "ascii" + str(self.config.hist_file), + getpreferredencoding() or "ascii", ) except OSError: pass @@ -749,7 +769,9 @@ def complete(self, tab: bool = False) -> Optional[bool]: else: return False - def format_docstring(self, docstring: str, width: int, height: int) -> List[str]: + def format_docstring( + self, docstring: str, width: int, height: int + ) -> List[str]: """Take a string and try to format it into a sane list of strings to be put into the suggestion box.""" From 7d4f80d6a37519a34cdcc3d591dc0d37280bda21 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Fri, 15 Apr 2022 13:14:41 -0400 Subject: [PATCH 1409/1650] good-enough solutions --- bpython/cli.py | 9 +++++++-- bpython/curtsiesfrontend/repl.py | 9 +++++++-- bpython/repl.py | 32 +++++++++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index d081e9405..6c15f5e2e 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1101,7 +1101,12 @@ def print_line( if self.highlighted_paren is not None: # Clear previous highlighted paren - self.reprint_line(*self.highlighted_paren) + + lineno = self.highlighted_paren[0] + tokens = self.highlighted_paren[1] + # mypy thinks tokens is List[Tuple[_TokenType, str]] + # but it is supposed to be MutableMapping[_TokenType, str] + self.reprint_line(lineno, tokens) self.highlighted_paren = None if self.config.syntax and (not self.paste_mode or newline): @@ -1221,7 +1226,7 @@ def repl(self) -> Tuple[Any, ...]: return self.exit_value def reprint_line( - self, lineno: int, tokens: MutableMapping[_TokenType, str] + self, lineno: int, tokens: List[Tuple[_TokenType, str]] ) -> None: """Helper function for paren highlighting: Reprint line at offset `lineno` in current input buffer.""" diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 59dcd481d..0d318fd1b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -13,9 +13,12 @@ import unicodedata from enum import Enum from types import TracebackType -from typing import Dict, Any, List, Optional, Tuple, Union, cast, Type +from typing import Dict, Any, List, Optional, Tuple, Union, cast, Type, TYPE_CHECKING from .._typing_compat import Literal +if TYPE_CHECKING: + from ..repl import Interpreter + import blessings import greenlet from curtsies import ( @@ -357,7 +360,9 @@ def __init__( ) self.edit_keys = edit_keys.mapping_with_config(config, key_dispatch) logger.debug("starting parent init") - super().__init__(interp, config) + # interp is a subclass of repl.Interpreter, so it definitely, + # implements the methods of Interpreter! + super().__init__(cast('Interpreter', interp), config) self.formatter = BPythonFormatter(config.color_scheme) diff --git a/bpython/repl.py b/bpython/repl.py index 31294bde7..1f4b6fc6e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -48,6 +48,7 @@ MutableMapping, Callable, Dict, + TYPE_CHECKING, ) from ._typing_compat import Literal @@ -61,7 +62,10 @@ have_pyperclip = False from . import autocomplete, inspection, simpleeval -from .cli import Statusbar + +if TYPE_CHECKING: + from .cli import Statusbar + from .config import getpreferredencoding, Config from .formatter import Parenthesis from .history import History @@ -365,7 +369,7 @@ def clear(self) -> None: class Interaction: - def __init__(self, config: Config, statusbar: Optional[Statusbar] = None): + def __init__(self, config: Config, statusbar: Optional['Statusbar'] = None): self.config = config if statusbar: @@ -418,6 +422,29 @@ class Repl: XXX Subclasses should implement echo, current_line, cw """ + @abstractmethod + @property + def current_line(self): + pass + + @abstractmethod + @property + def cursor_offset(self): + pass + + @abstractmethod + def reevaluate(self): + pass + + @abstractmethod + def reprint_line( + self, lineno: int, tokens: List[Tuple[_TokenType, str]] + ) -> None: + pass + + # not actually defined, subclasses must define + cpos: int + def __init__(self, interp: Interpreter, config: Config): """Initialise the repl. @@ -425,7 +452,6 @@ def __init__(self, interp: Interpreter, config: Config): config is a populated bpython.config.Struct. """ - self.config = config self.cut_buffer = "" self.buffer: List[str] = [] From d1891d279879c48b84498bead4e78f8629a45e7e Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Fri, 15 Apr 2022 12:23:37 -0500 Subject: [PATCH 1410/1650] formatted with black --- bpython/curtsiesfrontend/repl.py | 14 ++++++++++++-- bpython/repl.py | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 0d318fd1b..696e84bf7 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -13,7 +13,17 @@ import unicodedata from enum import Enum from types import TracebackType -from typing import Dict, Any, List, Optional, Tuple, Union, cast, Type, TYPE_CHECKING +from typing import ( + Dict, + Any, + List, + Optional, + Tuple, + Union, + cast, + Type, + TYPE_CHECKING, +) from .._typing_compat import Literal if TYPE_CHECKING: @@ -362,7 +372,7 @@ def __init__( logger.debug("starting parent init") # interp is a subclass of repl.Interpreter, so it definitely, # implements the methods of Interpreter! - super().__init__(cast('Interpreter', interp), config) + super().__init__(cast("Interpreter", interp), config) self.formatter = BPythonFormatter(config.color_scheme) diff --git a/bpython/repl.py b/bpython/repl.py index 1f4b6fc6e..19062a1ef 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -369,7 +369,7 @@ def clear(self) -> None: class Interaction: - def __init__(self, config: Config, statusbar: Optional['Statusbar'] = None): + def __init__(self, config: Config, statusbar: Optional["Statusbar"] = None): self.config = config if statusbar: From 1745ad5e2845751ad7fddd4b8327cfff1c015fc8 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Fri, 15 Apr 2022 12:39:57 -0500 Subject: [PATCH 1411/1650] hide properties under type checking --- bpython/repl.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 19062a1ef..bf9ee82c1 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -422,28 +422,29 @@ class Repl: XXX Subclasses should implement echo, current_line, cw """ - @abstractmethod - @property - def current_line(self): - pass + if TYPE_CHECKING: + @property + @abstractmethod + def current_line(self): + pass - @abstractmethod - @property - def cursor_offset(self): - pass + @property + @abstractmethod + def cursor_offset(self): + pass - @abstractmethod - def reevaluate(self): - pass + @abstractmethod + def reevaluate(self): + pass - @abstractmethod - def reprint_line( - self, lineno: int, tokens: List[Tuple[_TokenType, str]] - ) -> None: - pass + @abstractmethod + def reprint_line( + self, lineno: int, tokens: List[Tuple[_TokenType, str]] + ) -> None: + pass - # not actually defined, subclasses must define - cpos: int + # not actually defined, subclasses must define + cpos: int def __init__(self, interp: Interpreter, config: Config): """Initialise the repl. From a832ea35de4de1e00b8de6903b722c27df1b3def Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Fri, 15 Apr 2022 16:58:44 -0500 Subject: [PATCH 1412/1650] Found and fixed the mock error, still need to address the matches_iter error --- bpython/cli.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 6c15f5e2e..b1bff1269 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -68,6 +68,7 @@ Tuple, Collection, Dict, + Literal, ) import unicodedata from dataclasses import dataclass @@ -1330,7 +1331,7 @@ def show_list( arg_pos: Union[str, int, None], topline: Any = None, # Named tuples don't play nice with mypy formatter: Optional[Callable] = None, - current_item: Optional[str] = None, + current_item: Union[str, Literal[False]] = None, ) -> None: v_items: Collection shared = ShowListState() @@ -1527,9 +1528,8 @@ def tab(self, back: bool = False) -> bool: # 4. swap current word for a match list item elif self.matches_iter.matches: - n: str = next(self.matches_iter) - current_match: Optional[str] = ( - back and self.matches_iter.previous() or n + current_match: Union[str, Literal[False]] = ( + back and self.matches_iter.previous() or next(self.matches_iter) ) try: self.show_list( From 6f4b3c2e5a6f8eb0659d44e9f31578147d975dc7 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Fri, 15 Apr 2022 20:18:02 -0500 Subject: [PATCH 1413/1650] Fixed pytest errors --- bpython/cli.py | 16 ++++++++-------- bpython/repl.py | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index b1bff1269..db090cca2 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -514,12 +514,12 @@ def complete(self, tab: bool = False) -> None: list_win_visible = repl.Repl.complete(self, tab) - f = None - if self.matches_iter.completer: - f = self.matches_iter.completer.format - if list_win_visible: try: + f = None + if self.matches_iter.completer: + f = self.matches_iter.completer.format + self.show_list( self.matches_iter.matches, self.arg_pos, @@ -1494,10 +1494,6 @@ def tab(self, back: bool = False) -> bool: and don't indent if there are only whitespace in the line. """ - f = None - if self.matches_iter.completer: - f = self.matches_iter.completer.format - # 1. check if we should add a tab character if self.atbol() and not back: x_pos = len(self.s) - self.cpos @@ -1532,6 +1528,10 @@ def tab(self, back: bool = False) -> bool: back and self.matches_iter.previous() or next(self.matches_iter) ) try: + f = None + if self.matches_iter.completer: + f = self.matches_iter.completer.format + self.show_list( self.matches_iter.matches, self.arg_pos, diff --git a/bpython/repl.py b/bpython/repl.py index bf9ee82c1..2da6a9a13 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -423,6 +423,7 @@ class Repl: """ if TYPE_CHECKING: + @property @abstractmethod def current_line(self): From bcd3c31a22ee1e73d89c9f1117b14ec979312170 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Fri, 15 Apr 2022 20:25:57 -0500 Subject: [PATCH 1414/1650] Imported Literal using _typing_compat instead of typing --- bpython/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/cli.py b/bpython/cli.py index db090cca2..4f9b50c83 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -68,8 +68,8 @@ Tuple, Collection, Dict, - Literal, ) +from ._typing_compat import Literal import unicodedata from dataclasses import dataclass From d8d68d20018a55c8062e8d5a0468a2e6c0980fd0 Mon Sep 17 00:00:00 2001 From: Ben-Reg <3612364+Ben-Reg@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:16:59 -0500 Subject: [PATCH 1415/1650] Changed type hinting on curses windows to be compatible with python 3.7 --- bpython/cli.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 4f9b50c83..8bace509a 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -68,7 +68,12 @@ Tuple, Collection, Dict, + TYPE_CHECKING, ) + +if TYPE_CHECKING: + from _curses import _CursesWindow + from ._typing_compat import Literal import unicodedata from dataclasses import dataclass @@ -361,7 +366,7 @@ def file_prompt(self, s: str) -> Optional[str]: class CLIRepl(repl.Repl): def __init__( self, - scr: curses.window, + scr: "_CursesWindow", interp: repl.Interpreter, statusbar: "Statusbar", config: Config, @@ -371,7 +376,7 @@ def __init__( # mypy doesn't quite understand the difference between a class variable with a callable type and a method. # https://github.com/python/mypy/issues/2427 self.interp.writetb = self.writetb # type:ignore[assignment] - self.scr: curses.window = scr + self.scr: "_CursesWindow" = scr self.stdout_hist = "" # native str (bytes in Py2, unicode in Py3) self.list_win = newwin(get_colpair(config, "background"), 1, 1, 1, 1) self.cpos = 0 @@ -1631,8 +1636,8 @@ class Statusbar: def __init__( self, - scr: curses.window, - pwin: curses.window, + scr: "_CursesWindow", + pwin: "_CursesWindow", background: int, config: Config, s: Optional[str] = None, @@ -1640,7 +1645,7 @@ def __init__( ): """Initialise the statusbar and display the initial text (if any)""" self.size() - self.win: curses.window = newwin( + self.win: "_CursesWindow" = newwin( background, self.h, self.w, self.y, self.x ) @@ -1767,8 +1772,8 @@ def clear(self) -> None: def init_wins( - scr: curses.window, config: Config -) -> Tuple[curses.window, Statusbar]: + scr: "_CursesWindow", config: Config +) -> Tuple["_CursesWindow", Statusbar]: """Initialise the two windows (the main repl interface and the little status bar at the bottom with some stuff in it)""" # TODO: Document better what stuff is on the status bar. @@ -1803,12 +1808,12 @@ def init_wins( return main_win, statusbar -def sigwinch(unused_scr: curses.window) -> None: +def sigwinch(unused_scr: "_CursesWindow") -> None: global DO_RESIZE DO_RESIZE = True -def sigcont(unused_scr: curses.window) -> None: +def sigcont(unused_scr: "_CursesWindow") -> None: sigwinch(unused_scr) # Forces the redraw curses.ungetch("\x00") @@ -1933,7 +1938,7 @@ def __getitem__(self, k: Any) -> int: return self._val -def newwin(background: int, *args: int) -> curses.window: +def newwin(background: int, *args: int) -> "_CursesWindow": """Wrapper for curses.newwin to automatically set background colour on any newly created window.""" win = curses.newwin(*args) @@ -1967,7 +1972,7 @@ def curses_wrapper(func: Callable, *args: Any, **kwargs: Any) -> Any: def main_curses( - scr: curses.window, + scr: "_CursesWindow", args: List[str], config: Config, interactive: bool = True, From 9e0feb33fc5c1626ba19dbfba552bb669203c548 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 3 May 2022 00:32:15 +0200 Subject: [PATCH 1416/1650] Fix some type annotatations --- bpython/curtsies.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 7ffe90109..82adc15f0 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -55,9 +55,9 @@ class FullCurtsiesRepl(BaseRepl): def __init__( self, config: Config, - locals_: Optional[Dict[str, Any]], - banner: Optional[str], - interp: code.InteractiveInterpreter = None, + locals_: Optional[Dict[str, Any]] = None, + banner: Optional[str] = None, + interp: Optional[Interp] = None, ) -> None: self.input_generator = curtsies.input.Input( keynames="curtsies", sigint_event=True, paste_threshold=None @@ -182,10 +182,10 @@ def mainloop( def main( - args: List[str] = None, - locals_: Dict[str, Any] = None, - banner: str = None, - welcome_message: str = None, + args: Optional[List[str]] = None, + locals_: Optional[Dict[str, Any]] = None, + banner: Optional[str] = None, + welcome_message: Optional[str] = None, ) -> Any: """ banner is displayed directly after the version information. From cca075a6346fda5177146ca6dbbbcde0a29ea3a9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 3 May 2022 00:42:06 +0200 Subject: [PATCH 1417/1650] Remove unused function --- bpython/cli.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 8bace509a..21d79ba31 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -123,24 +123,6 @@ class ShowListState: wl: int = 0 -def calculate_screen_lines( - tokens: MutableMapping[_TokenType, str], width: int, cursor: int = 0 -) -> int: - """Given a stream of tokens and a screen width plus an optional - initial cursor position, return the amount of needed lines on the - screen.""" - lines = 1 - pos = cursor - for (token, value) in tokens: - if token is Token.Text and value == "\n": - lines += 1 - else: - pos += len(value) - lines += pos // width - pos %= width - return lines - - def forward_if_not_current(func: F) -> F: @functools.wraps(func) def newfunc(self, *args, **kwargs): # type: ignore From 069a0a655facba02b74362da2684a2504d1d7fa6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 3 May 2022 00:42:18 +0200 Subject: [PATCH 1418/1650] Set to empty string if no removal URL is available --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 2da6a9a13..a74fa206d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -956,7 +956,7 @@ def do_pastebin(self, s) -> Optional[str]: self.prev_pastebin_content = s self.prev_pastebin_url = paste_url - self.prev_removal_url = removal_url + self.prev_removal_url = removal_url if removal_url is not None else "" if removal_url is not None: self.interact.notify( From a808521ad8301c237bde708d161d32daa49a7ec9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 00:11:32 +0200 Subject: [PATCH 1419/1650] Remove no longer used encoding --- bpython/cli.py | 2 +- bpython/curtsiesfrontend/interpreter.py | 3 +- bpython/repl.py | 44 +++++++------------------ bpython/test/test_interpreter.py | 11 ++----- bpython/urwid.py | 2 +- 5 files changed, 16 insertions(+), 46 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 21d79ba31..3291be090 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -2005,7 +2005,7 @@ def main_curses( curses.raw(True) main_win, statusbar = init_wins(scr, config) - interpreter = repl.Interpreter(locals_, getpreferredencoding()) + interpreter = repl.Interpreter(locals_) clirepl = CLIRepl(main_win, interpreter, statusbar, config, idle) clirepl._C = cols diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 91dba96ae..1b3133459 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -63,14 +63,13 @@ class Interp(ReplInterpreter): def __init__( self, locals: Optional[Dict[str, Any]] = None, - encoding: Optional[str] = None, ) -> None: """Constructor. We include an argument for the outfile to pass to the formatter for it to write to. """ - super().__init__(locals, encoding) + super().__init__(locals) # typically changed after being instantiated # but used when interpreter used corresponding REPL diff --git a/bpython/repl.py b/bpython/repl.py index a74fa206d..1f318f8f4 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -110,9 +110,8 @@ class Interpreter(code.InteractiveInterpreter): def __init__( self, - locals: Optional[MutableMapping[str, str]] = None, - encoding: Optional[str] = None, - ): + locals: Optional[MutableMapping[str, Any]] = None, + ) -> None: """Constructor. The optional 'locals' argument specifies the dictionary in which code @@ -126,14 +125,8 @@ def __init__( callback can be added to the Interpreter instance afterwards - more specifically, this is so that autoindentation does not occur after a traceback. - - encoding is only used in Python 2, where it may be necessary to add an - encoding comment to a source bytestring before running it. - encoding must be a bytestring in Python 2 because it will be templated - into a bytestring source as part of an encoding comment. """ - self.encoding = encoding or getpreferredencoding() self.syntaxerror_callback: Optional[Callable] = None if locals is None: @@ -145,36 +138,21 @@ def __init__( super().__init__(locals) self.timer = RuntimeTimer() - def runsource(self, source, filename=None, symbol="single", encode="auto"): + def runsource( + self, + source: str, + filename: Optional[str] = None, + symbol: str = "single", + ) -> bool: """Execute Python code. source, filename and symbol are passed on to - code.InteractiveInterpreter.runsource. If encode is True, - an encoding comment will be added to the source. - On Python 3.X, encode will be ignored. - - encode should only be used for interactive interpreter input, - files should always already have an encoding comment or be ASCII. - By default an encoding line will be added if no filename is given. - - source must be a string - - Because adding an encoding comment to a unicode string in Python 2 - would cause a syntax error to be thrown which would reference code - the user did not write, setting encoding to True when source is a - unicode string in Python 2 will throw a ValueError.""" - if encode and filename is not None: - # files have encoding comments or implicit encoding of ASCII - if encode != "auto": - raise ValueError("shouldn't add encoding line to file contents") - encode = False + code.InteractiveInterpreter.runsource.""" if filename is None: filename = filename_for_console_input(source) with self.timer: - return code.InteractiveInterpreter.runsource( - self, source, filename, symbol - ) + return super().runsource(source, filename, symbol) def showsyntaxerror(self, filename=None): """Override the regular handler, the code's copied and pasted from @@ -532,7 +510,7 @@ def startup(self) -> None: encoding = inspection.get_encoding_file(filename) with open(filename, encoding=encoding) as f: source = f.read() - self.interp.runsource(source, filename, "exec", encode=False) + self.interp.runsource(source, filename, "exec") def current_string(self, concatenate=False): """If the line ends in a string get it, otherwise return ''""" diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 4c18f8fda..281ad5fa5 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -38,7 +38,7 @@ def test_syntaxerror(self): if (3, 10, 1) <= sys.version_info[:3]: expected = ( " File " - + green('""') + + green('""') + ", line " + bold(magenta("1")) + "\n 1.1.1.1\n ^^\n" @@ -50,7 +50,7 @@ def test_syntaxerror(self): elif (3, 10) <= sys.version_info[:2]: expected = ( " File " - + green('""') + + green('""') + ", line " + bold(magenta("1")) + "\n 1.1.1.1\n ^^^^^\n" @@ -129,13 +129,6 @@ def gfunc(): self.assertMultiLineEqual(str(plain("").join(a)), str(expected)) self.assertEqual(plain("").join(a), expected) - def test_runsource_bytes_over_128_syntax_error_py3(self): - i = interpreter.Interp(encoding="latin-1") - i.showsyntaxerror = mock.Mock(return_value=None) - - i.runsource("a = b'\xfe'") - i.showsyntaxerror.assert_called_with(mock.ANY) - def test_getsource_works_on_interactively_defined_functions(self): source = "def foo(x):\n return x + 1\n" i = interpreter.Interp() diff --git a/bpython/urwid.py b/bpython/urwid.py index 33d1f4b8c..ec7038e80 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -1243,7 +1243,7 @@ def options_callback(group): extend_locals["service"] = serv reactor.callWhenRunning(serv.startService) exec_args = [] - interpreter = repl.Interpreter(locals_, locale.getpreferredencoding()) + interpreter = repl.Interpreter(locals_) # TODO: replace with something less hack-ish interpreter.locals.update(extend_locals) From 8caa915e2f0f7e96b5945c01443e418e1fb3494e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 00:27:08 +0200 Subject: [PATCH 1420/1650] Make Interaction an abstract base class --- bpython/cli.py | 18 +++++------ bpython/curtsiesfrontend/interaction.py | 4 +-- bpython/repl.py | 41 ++++++++++++++++++------- bpython/urwid.py | 7 ++++- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 3291be090..5d8b0ed4b 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -319,14 +319,14 @@ def make_colors(config: Config) -> Dict[str, int]: class CLIInteraction(repl.Interaction): - def __init__(self, config: Config, statusbar: Optional["Statusbar"] = None): - super().__init__(config, statusbar) + def __init__(self, config: Config, statusbar: "Statusbar"): + super().__init__(config) + self.statusbar = statusbar def confirm(self, q: str) -> bool: """Ask for yes or no and return boolean""" try: - if self.statusbar: - reply = self.statusbar.prompt(q) + reply = self.statusbar.prompt(q) except ValueError: return False @@ -335,14 +335,10 @@ def confirm(self, q: str) -> bool: def notify( self, s: str, n: int = 10, wait_for_keypress: bool = False ) -> None: - if self.statusbar: - self.statusbar.message(s, n) + self.statusbar.message(s, n) def file_prompt(self, s: str) -> Optional[str]: - if self.statusbar: - return self.statusbar.prompt(s) - else: - return None + return self.statusbar.prompt(s) class CLIRepl(repl.Repl): @@ -1675,7 +1671,7 @@ def check(self) -> None: self.settext(self._s) - def message(self, s: str, n: int = 3) -> None: + def message(self, s: str, n: float = 3.0) -> None: """Display a message for a short n seconds on the statusbar and return it to its original state.""" self.timer = int(time.time() + n) diff --git a/bpython/curtsiesfrontend/interaction.py b/bpython/curtsiesfrontend/interaction.py index 79622d149..17b178de6 100644 --- a/bpython/curtsiesfrontend/interaction.py +++ b/bpython/curtsiesfrontend/interaction.py @@ -39,7 +39,7 @@ def __init__( self.prompt = "" self._message = "" self.message_start_time = time.time() - self.message_time = 3 + self.message_time = 3.0 self.permanent_stack = [] if permanent_text: self.permanent_stack.append(permanent_text) @@ -149,7 +149,7 @@ def should_show_message(self): return bool(self.current_line) # interaction interface - should be called from other greenlets - def notify(self, msg, n=3, wait_for_keypress=False): + def notify(self, msg, n=3.0, wait_for_keypress=False): self.request_context = greenlet.getcurrent() self.message_time = n self.message(msg, schedule_refresh=wait_for_keypress) diff --git a/bpython/repl.py b/bpython/repl.py index 1f318f8f4..f5c21def1 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -21,6 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +import abc import code import inspect import os @@ -346,21 +347,39 @@ def clear(self) -> None: self.index = -1 -class Interaction: - def __init__(self, config: Config, statusbar: Optional["Statusbar"] = None): +class Interaction(metaclass=abc.ABCMeta): + def __init__(self, config: Config): self.config = config - if statusbar: - self.statusbar = statusbar + @abc.abstractmethod + def confirm(self, s: str) -> bool: + pass - def confirm(self, s): - raise NotImplementedError + @abc.abstractmethod + def notify( + self, s: str, n: float = 10.0, wait_for_keypress: bool = False + ) -> None: + pass + + @abc.abstractmethod + def file_prompt(self, s: str) -> Optional[str]: + pass + + +class NoInteraction(Interaction): + def __init__(self, config: Config): + super().__init__(config) - def notify(self, s, n=10, wait_for_keypress=False): - raise NotImplementedError + def confirm(self, s: str) -> bool: + return False + + def notify( + self, s: str, n: float = 10.0, wait_for_keypress: bool = False + ) -> None: + pass - def file_prompt(self, s): - raise NotImplementedError + def file_prompt(self, s: str) -> Optional[str]: + return None class SourceNotFound(Exception): @@ -458,7 +477,7 @@ def __init__(self, interp: Interpreter, config: Config): ] = None self._C: Dict[str, int] = {} self.prev_block_finished: int = 0 - self.interact = Interaction(self.config) + self.interact: Interaction = NoInteraction(self.config) # previous pastebin content to prevent duplicate pastes, filled on call # to repl.pastebin self.prev_pastebin_content = "" diff --git a/bpython/urwid.py b/bpython/urwid.py index ec7038e80..0a2ffe31b 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -38,6 +38,7 @@ import locale import signal import urwid +from typing import Optional from . import args as bpargs, repl, translations from .formatter import theme_map @@ -526,7 +527,8 @@ def render(self, size, focus=False): class URWIDInteraction(repl.Interaction): def __init__(self, config, statusbar, frame): - super().__init__(config, statusbar) + super().__init__(config) + self.statusbar = statusbar self.frame = frame urwid.connect_signal(statusbar, "prompt_result", self._prompt_result) self.callback = None @@ -563,6 +565,9 @@ def _prompt_result(self, text): self.callback = None callback(text) + def file_prompt(self, s: str) -> Optional[str]: + raise NotImplementedError + class URWIDRepl(repl.Repl): From deb757d880791d56421216991cc949e40511b669 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 00:27:53 +0200 Subject: [PATCH 1421/1650] Add more type annotations and fix some mypy errors --- bpython/cli.py | 2 +- bpython/repl.py | 52 ++++++++++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 5d8b0ed4b..559b63e7b 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -333,7 +333,7 @@ def confirm(self, q: str) -> bool: return reply.lower() in (_("y"), _("yes")) def notify( - self, s: str, n: int = 10, wait_for_keypress: bool = False + self, s: str, n: float = 10.0, wait_for_keypress: bool = False ) -> None: self.statusbar.message(s, n) diff --git a/bpython/repl.py b/bpython/repl.py index f5c21def1..8294f2c8e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -39,6 +39,7 @@ from pathlib import Path from types import ModuleType, TracebackType from typing import ( + Iterable, cast, List, Tuple, @@ -155,7 +156,7 @@ def runsource( with self.timer: return super().runsource(source, filename, symbol) - def showsyntaxerror(self, filename=None): + def showsyntaxerror(self, filename: Optional[str] = None) -> None: """Override the regular handler, the code's copied and pasted from code.py, as per showtraceback, but with the syntaxerror callback called and the text in a pretty colour.""" @@ -182,7 +183,7 @@ def showsyntaxerror(self, filename=None): exc_formatted = traceback.format_exception_only(exc_type, value) self.writetb(exc_formatted) - def showtraceback(self): + def showtraceback(self) -> None: """This needs to override the default traceback thing so it can put it into a pretty colour and maybe other stuff, I don't know""" @@ -194,11 +195,10 @@ def showtraceback(self): tblist = traceback.extract_tb(tb) del tblist[:1] - for i, (fname, lineno, module, something) in enumerate(tblist): - # strip linecache line number - if self.bpython_input_re.match(fname): - fname = "" - tblist[i] = (fname, lineno, module, something) + for frame in tblist: + if self.bpython_input_re.match(frame.filename): + # strip linecache line number + frame.filename = "" l = traceback.format_list(tblist) if l: @@ -209,7 +209,7 @@ def showtraceback(self): self.writetb(l) - def writetb(self, lines): + def writetb(self, lines: Iterable[str]) -> None: """This outputs the traceback and should be overridden for anything fancy.""" for line in lines: @@ -463,9 +463,8 @@ def __init__(self, interp: Interpreter, config: Config): # all input and output, stored as old style format strings # (\x01, \x02, ...) for cli.py self.screen_hist: List[str] = [] - self.history: List[ - str - ] = [] # commands executed since beginning of session + # commands executed since beginning of session + self.history: List[str] = [] self.redo_stack: List[str] = [] self.evaluating = False self.matches_iter = MatchesIterator() @@ -870,25 +869,22 @@ def write2file(self) -> None: self.interact.notify(_("Save cancelled.")) return - fn = Path(fn).expanduser() - if fn.suffix != ".py" and self.config.save_append_py: + path = Path(fn).expanduser() + if path.suffix != ".py" and self.config.save_append_py: # fn.with_suffix(".py") does not append if fn has a non-empty suffix - fn = Path(f"{fn}.py") + path = Path(f"{path}.py") mode = "w" - if fn.exists(): - mode = self.interact.file_prompt( + if path.exists(): + new_mode = self.interact.file_prompt( _( - "%s already exists. Do you " - "want to (c)ancel, " - " (o)verwrite or " - "(a)ppend? " + "%s already exists. Do you want to (c)ancel, (o)verwrite or (a)ppend? " ) - % (fn,) + % (path,) ) - if mode in ("o", "overwrite", _("overwrite")): + if new_mode in ("o", "overwrite", _("overwrite")): mode = "w" - elif mode in ("a", "append", _("append")): + elif new_mode in ("a", "append", _("append")): mode = "a" else: self.interact.notify(_("Save cancelled.")) @@ -897,12 +893,12 @@ def write2file(self) -> None: stdout_text = self.get_session_formatted_for_file() try: - with open(fn, mode) as f: + with open(path, mode) as f: f.write(stdout_text) except OSError as e: - self.interact.notify(_("Error writing file '%s': %s") % (fn, e)) + self.interact.notify(_("Error writing file '%s': %s") % (path, e)) else: - self.interact.notify(_("Saved to %s.") % (fn,)) + self.interact.notify(_("Saved to %s.") % (path,)) def copy2clipboard(self) -> None: """Copy current content to clipboard.""" @@ -1003,6 +999,10 @@ def prompt_undo(self) -> int: _("Undo how many lines? (Undo will take up to ~%.1f seconds) [1]") % (est,) ) + if m is None: + self.interact.notify(_("Undo canceled"), 0.1) + return 0 + try: if m == "": m = "1" From 59a8095a962cebe9a17783e872ca15a19a326fe0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 01:05:55 +0200 Subject: [PATCH 1422/1650] Handle end_lineno and end_offset in SyntaxError --- bpython/repl.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 8294f2c8e..d3b646766 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -166,20 +166,21 @@ def showsyntaxerror(self, filename: Optional[str] = None) -> None: exc_type, value, sys.last_traceback = sys.exc_info() sys.last_type = exc_type sys.last_value = value - if filename and exc_type is SyntaxError: - # Work hard to stuff the correct filename in the exception - try: - msg, (dummy_filename, lineno, offset, line) = value.args - except: - # Not the format we expect; leave it alone - pass - else: - # Stuff in the right filename and right lineno - # strip linecache line number - if self.bpython_input_re.match(filename): - filename = "" - value = SyntaxError(msg, (filename, lineno, offset, line)) - sys.last_value = value + if ( + filename + and exc_type is SyntaxError + and value is not None + and len(value.args) >= 4 + ): + msg = str(value) + lineno = value.args[1] + offset = value.args[2] + line = value.args[3] + # strip linechache line number + if self.bpython_input_re.match(filename): + filename = "" + value = SyntaxError(msg, (filename, lineno, offset, line)) + sys.last_value = value exc_formatted = traceback.format_exception_only(exc_type, value) self.writetb(exc_formatted) From 937626dd46abcc50cd8192e22c8185955a5bf780 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 01:25:42 +0200 Subject: [PATCH 1423/1650] Improve handling of SyntaxErrors is now detected and replaced with in more cases. --- bpython/repl.py | 17 +++++------------ bpython/test/test_interpreter.py | 4 ++-- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index d3b646766..fda799280 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -166,20 +166,13 @@ def showsyntaxerror(self, filename: Optional[str] = None) -> None: exc_type, value, sys.last_traceback = sys.exc_info() sys.last_type = exc_type sys.last_value = value - if ( - filename - and exc_type is SyntaxError - and value is not None - and len(value.args) >= 4 - ): - msg = str(value) - lineno = value.args[1] - offset = value.args[2] - line = value.args[3] + if filename and exc_type is SyntaxError and value is not None: + msg = value.args[0] + args = list(value.args[1]) # strip linechache line number if self.bpython_input_re.match(filename): - filename = "" - value = SyntaxError(msg, (filename, lineno, offset, line)) + args[0] = "" + value = SyntaxError(msg, tuple(args)) sys.last_value = value exc_formatted = traceback.format_exception_only(exc_type, value) self.writetb(exc_formatted) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 281ad5fa5..65b60a925 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -38,7 +38,7 @@ def test_syntaxerror(self): if (3, 10, 1) <= sys.version_info[:3]: expected = ( " File " - + green('""') + + green('""') + ", line " + bold(magenta("1")) + "\n 1.1.1.1\n ^^\n" @@ -50,7 +50,7 @@ def test_syntaxerror(self): elif (3, 10) <= sys.version_info[:2]: expected = ( " File " - + green('""') + + green('""') + ", line " + bold(magenta("1")) + "\n 1.1.1.1\n ^^^^^\n" From cec06780ef158a14fb3fc6b372c30717821cd6cc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 01:25:51 +0200 Subject: [PATCH 1424/1650] GA: do not fail fast --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 62ebac0f7..89edab7c8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,6 +12,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: ${{ matrix.python-version == 'pypy-3.7' }} strategy: + fail-fast: false matrix: python-version: [3.7, 3.8, 3.9, "3.10", "pypy-3.7"] steps: From 0ada13d6645d80c18399c738d01e75380b6c431f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 09:52:41 +0200 Subject: [PATCH 1425/1650] Provide more type annotations --- bpython/repl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index fda799280..41c28d5a8 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -417,12 +417,12 @@ class Repl: @property @abstractmethod - def current_line(self): + def current_line(self) -> str: pass @property @abstractmethod - def cursor_offset(self): + def cursor_offset(self) -> int: pass @abstractmethod From e906df7c2c437970b95fad75dca925cb41895f0d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 10:31:42 +0200 Subject: [PATCH 1426/1650] Refactor _funcname_and_argnum to avoid Exception-based control flow --- bpython/repl.py | 86 ++++++++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 41c28d5a8..6e8278225 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -35,6 +35,7 @@ import time import traceback from abc import abstractmethod +from dataclasses import dataclass from itertools import takewhile from pathlib import Path from types import ModuleType, TracebackType @@ -380,6 +381,17 @@ class SourceNotFound(Exception): """Exception raised when the requested source could not be found.""" +@dataclass +class _FuncExpr: + """Stack element in Repl._funcname_and_argnum""" + + full_expr: str + function_expr: str + arg_number: int + opening: str + keyword: Optional[str] = None + + class Repl: """Implements the necessary guff for a Python-repl-alike interface @@ -564,37 +576,37 @@ def get_object(self, name): return obj @classmethod - def _funcname_and_argnum(cls, line): + def _funcname_and_argnum( + cls, line: str + ) -> Tuple[Optional[str], Optional[Union[str, int]]]: """Parse out the current function name and arg from a line of code.""" - # each list in stack: - # [full_expr, function_expr, arg_number, opening] - # arg_number may be a string if we've encountered a keyword - # argument so we're done counting - stack = [["", "", 0, ""]] + # each element in stack is a _FuncExpr instance + # if keyword is not None, we've encountered a keyword and so we're done counting + stack = [_FuncExpr("", "", 0, "")] try: for (token, value) in Python3Lexer().get_tokens(line): if token is Token.Punctuation: if value in "([{": - stack.append(["", "", 0, value]) + stack.append(_FuncExpr("", "", 0, value)) elif value in ")]}": - full, _, _, start = stack.pop() - expr = start + full + value - stack[-1][1] += expr - stack[-1][0] += expr + element = stack.pop() + expr = element.opening + element.full_expr + value + stack[-1].function_expr += expr + stack[-1].full_expr += expr elif value == ",": - try: - stack[-1][2] += 1 - except TypeError: - stack[-1][2] = "" - stack[-1][1] = "" - stack[-1][0] += value - elif value == ":" and stack[-1][3] == "lambda": - expr = stack.pop()[0] + ":" - stack[-1][1] += expr - stack[-1][0] += expr + if stack[-1].keyword is None: + stack[-1].arg_number += 1 + else: + stack[-1].keyword = "" + stack[-1].function_expr = "" + stack[-1].full_expr += value + elif value == ":" and stack[-1].opening == "lambda": + expr = stack.pop().full_expr + ":" + stack[-1].function_expr += expr + stack[-1].full_expr += expr else: - stack[-1][1] = "" - stack[-1][0] += value + stack[-1].function_expr = "" + stack[-1].full_expr += value elif ( token is Token.Number or token in Token.Number.subtypes @@ -603,25 +615,25 @@ def _funcname_and_argnum(cls, line): or token is Token.Operator and value == "." ): - stack[-1][1] += value - stack[-1][0] += value + stack[-1].function_expr += value + stack[-1].full_expr += value elif token is Token.Operator and value == "=": - stack[-1][2] = stack[-1][1] - stack[-1][1] = "" - stack[-1][0] += value + stack[-1].keyword = stack[-1].function_expr + stack[-1].function_expr = "" + stack[-1].full_expr += value elif token is Token.Number or token in Token.Number.subtypes: - stack[-1][1] = value - stack[-1][0] += value + stack[-1].function_expr = value + stack[-1].full_expr += value elif token is Token.Keyword and value == "lambda": - stack.append([value, "", 0, value]) + stack.append(_FuncExpr(value, "", 0, value)) else: - stack[-1][1] = "" - stack[-1][0] += value - while stack[-1][3] in "[{": + stack[-1].function_expr = "" + stack[-1].full_expr += value + while stack[-1].opening in "[{": stack.pop() - _, _, arg_number, _ = stack.pop() - _, func, _, _ = stack.pop() - return func, arg_number + elem1 = stack.pop() + elem2 = stack.pop() + return elem2.function_expr, elem1.keyword or elem1.arg_number except IndexError: return None, None From b86de33c17413b6fa7cbb0597bd491605dc85412 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 10:59:00 +0200 Subject: [PATCH 1427/1650] Make implementations of current_line and cursor_offset consistent --- bpython/cli.py | 14 -------- bpython/curtsiesfrontend/repl.py | 18 +++------- bpython/repl.py | 60 ++++++++++++++++++++++---------- bpython/test/test_repl.py | 30 ++++++++++++++-- 4 files changed, 73 insertions(+), 49 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 559b63e7b..5383a7c38 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -383,13 +383,6 @@ def _get_cursor_offset(self) -> int: def _set_cursor_offset(self, offset: int) -> None: self.cpos = len(self.s) - offset - cursor_offset = property( - _get_cursor_offset, - _set_cursor_offset, - None, - "The cursor offset from the beginning of the line", - ) - def addstr(self, s: str) -> None: """Add a string to the current input line and figure out where it should go, depending on the cursor position.""" @@ -539,13 +532,6 @@ def _get_current_line(self) -> str: def _set_current_line(self, line: str) -> None: self.s = line - current_line = property( - _get_current_line, - _set_current_line, - None, - "The characters of the current line", - ) - def cut_to_buffer(self) -> None: """Clear from cursor to end of line, placing into cut buffer""" self.cut_buffer = self.s[-self.cpos :] diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 696e84bf7..b8c7f0164 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1866,18 +1866,13 @@ def __repr__(self): lines scrolled down: {self.scroll_offset} >""" - @property - def current_line(self): + def _get_current_line(self) -> str: """The current line""" return self._current_line - @current_line.setter - def current_line(self, value): - self._set_current_line(value) - def _set_current_line( self, - line, + line: str, update_completion=True, reset_rl_history=True, clear_special_mode=True, @@ -1895,18 +1890,13 @@ def _set_current_line( self.special_mode = None self.unhighlight_paren() - @property - def cursor_offset(self): + def _get_cursor_offset(self) -> int: """The current cursor offset from the front of the "line".""" return self._cursor_offset - @cursor_offset.setter - def cursor_offset(self, value): - self._set_cursor_offset(value) - def _set_cursor_offset( self, - offset, + offset: int, update_completion=True, reset_rl_history=False, clear_special_mode=True, diff --git a/bpython/repl.py b/bpython/repl.py index 6e8278225..6420a1780 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -392,7 +392,7 @@ class _FuncExpr: keyword: Optional[str] = None -class Repl: +class Repl(metaclass=abc.ABCMeta): """Implements the necessary guff for a Python-repl-alike interface The execution of the code entered and all that stuff was taken from the @@ -425,27 +425,51 @@ class Repl: XXX Subclasses should implement echo, current_line, cw """ - if TYPE_CHECKING: + @abc.abstractmethod + def reevaluate(self): + pass - @property - @abstractmethod - def current_line(self) -> str: - pass + @abc.abstractmethod + def reprint_line( + self, lineno: int, tokens: List[Tuple[_TokenType, str]] + ) -> None: + pass - @property - @abstractmethod - def cursor_offset(self) -> int: - pass + @abc.abstractmethod + def _get_current_line(self) -> str: + pass - @abstractmethod - def reevaluate(self): - pass + @abc.abstractmethod + def _set_current_line(self, val: str) -> None: + pass - @abstractmethod - def reprint_line( - self, lineno: int, tokens: List[Tuple[_TokenType, str]] - ) -> None: - pass + @property + def current_line(self) -> str: + """The current line""" + return self._get_current_line() + + @current_line.setter + def current_line(self, value: str) -> None: + self._set_current_line(value) + + @abc.abstractmethod + def _get_cursor_offset(self) -> int: + pass + + @abc.abstractmethod + def _set_cursor_offset(self, val: int) -> None: + pass + + @property + def cursor_offset(self) -> int: + """The current cursor offset from the front of the "line".""" + return self._get_cursor_offset() + + @cursor_offset.setter + def cursor_offset(self, value: int) -> None: + self._set_cursor_offset(value) + + if TYPE_CHECKING: # not actually defined, subclasses must define cpos: int diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index a4241087d..63309364c 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -5,6 +5,7 @@ import tempfile import unittest +from typing import List, Tuple from itertools import islice from pathlib import Path from unittest import mock @@ -38,9 +39,32 @@ def reset(self): class FakeRepl(repl.Repl): def __init__(self, conf=None): - repl.Repl.__init__(self, repl.Interpreter(), setup_config(conf)) - self.current_line = "" - self.cursor_offset = 0 + super().__init__(repl.Interpreter(), setup_config(conf)) + self._current_line = "" + self._cursor_offset = 0 + + def _get_current_line(self) -> str: + return self._current_line + + def _set_current_line(self, val: str) -> None: + self._current_line = val + + def _get_cursor_offset(self) -> int: + return self._cursor_offset + + def _set_cursor_offset(self, val: int) -> None: + self._cursor_offset = val + + def getstdout(self) -> str: + raise NotImplementedError + + def reprint_line( + self, lineno: int, tokens: List[Tuple[repl._TokenType, str]] + ) -> None: + raise NotImplementedError + + def reevaluate(self): + raise NotImplementedError class FakeCliRepl(cli.CLIRepl, FakeRepl): From cc3bac7e552cb7d88d25bac0011caf045714126c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 11:09:05 +0200 Subject: [PATCH 1428/1650] Change filenames in History to Path --- bpython/history.py | 15 ++++++++------- bpython/repl.py | 6 +++--- bpython/test/test_history.py | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/bpython/history.py b/bpython/history.py index dfbab2ada..a870d4b29 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -22,9 +22,10 @@ # THE SOFTWARE. import os +from pathlib import Path import stat from itertools import islice, chain -from typing import Iterable, Optional, List, TextIO +from typing import Iterable, Optional, List, TextIO, Union from .translations import _ from .filelock import FileLock @@ -190,9 +191,9 @@ def reset(self) -> None: self.index = 0 self.saved_line = "" - def load(self, filename: str, encoding: str) -> None: + def load(self, filename: Path, encoding: str) -> None: with open(filename, encoding=encoding, errors="ignore") as hfile: - with FileLock(hfile, filename=filename): + with FileLock(hfile, filename=str(filename)): self.entries = self.load_from(hfile) def load_from(self, fd: TextIO) -> List[str]: @@ -201,14 +202,14 @@ def load_from(self, fd: TextIO) -> List[str]: self.append_to(entries, line) return entries if len(entries) else [""] - def save(self, filename: str, encoding: str, lines: int = 0) -> None: + def save(self, filename: Path, encoding: str, lines: int = 0) -> None: fd = os.open( filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, stat.S_IRUSR | stat.S_IWUSR, ) with open(fd, "w", encoding=encoding, errors="ignore") as hfile: - with FileLock(hfile, filename=filename): + with FileLock(hfile, filename=str(filename)): self.save_to(hfile, self.entries, lines) def save_to( @@ -221,7 +222,7 @@ def save_to( fd.write("\n") def append_reload_and_write( - self, s: str, filename: str, encoding: str + self, s: str, filename: Path, encoding: str ) -> None: if not self.hist_size: return self.append(s) @@ -233,7 +234,7 @@ def append_reload_and_write( stat.S_IRUSR | stat.S_IWUSR, ) with open(fd, "a+", encoding=encoding, errors="ignore") as hfile: - with FileLock(hfile, filename=filename): + with FileLock(hfile, filename=str(filename)): # read entries hfile.seek(0, os.SEEK_SET) entries = self.load_from(hfile) diff --git a/bpython/repl.py b/bpython/repl.py index 6420a1780..e0d42b1f9 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -520,7 +520,7 @@ def __init__(self, interp: Interpreter, config: Config): if self.config.hist_file.exists(): try: self.rl_history.load( - str(self.config.hist_file), + self.config.hist_file, getpreferredencoding() or "ascii", ) except OSError: @@ -744,7 +744,7 @@ def get_source_of_current_name(self) -> str: msg = _("No source code found for %s") % (self.current_line,) raise SourceNotFound(msg) - def set_docstring(self): + def set_docstring(self) -> None: self.docstring = None if not self.get_args(): self.funcprops = None @@ -1009,7 +1009,7 @@ def push(self, s, insert_into_history=True) -> bool: return more - def insert_into_history(self, s): + def insert_into_history(self, s: str): try: self.rl_history.append_reload_and_write( s, self.config.hist_file, getpreferredencoding() diff --git a/bpython/test/test_history.py b/bpython/test/test_history.py index 544a644eb..d810cf6be 100644 --- a/bpython/test/test_history.py +++ b/bpython/test/test_history.py @@ -86,7 +86,7 @@ def test_reset(self): class TestHistoryFileAccess(unittest.TestCase): def setUp(self): self.tempdir = tempfile.TemporaryDirectory() - self.filename = str(Path(self.tempdir.name) / "history_temp_file") + self.filename = Path(self.tempdir.name) / "history_temp_file" self.encoding = getpreferredencoding() with open( From 167712230a803edd7b34a1cb127926632c964a01 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 23:50:02 +0200 Subject: [PATCH 1429/1650] Simplify type annotations --- bpython/curtsiesfrontend/repl.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index b8c7f0164..c2b502fe6 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1,4 +1,3 @@ -import code import contextlib import errno import itertools @@ -22,13 +21,9 @@ Union, cast, Type, - TYPE_CHECKING, ) from .._typing_compat import Literal -if TYPE_CHECKING: - from ..repl import Interpreter - import blessings import greenlet from curtsies import ( @@ -327,7 +322,7 @@ def __init__( config: Config, locals_: Optional[Dict[str, Any]] = None, banner: Optional[str] = None, - interp: Optional[code.InteractiveInterpreter] = None, + interp: Optional[Interp] = None, orig_tcattrs: Optional[List[Any]] = None, ): """ @@ -372,7 +367,7 @@ def __init__( logger.debug("starting parent init") # interp is a subclass of repl.Interpreter, so it definitely, # implements the methods of Interpreter! - super().__init__(cast("Interpreter", interp), config) + super().__init__(interp, config) self.formatter = BPythonFormatter(config.color_scheme) From 2a95aa790aabe21dab4860b30e69f954627c5ed3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 23:50:11 +0200 Subject: [PATCH 1430/1650] Remove unused import --- bpython/curtsies.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 82adc15f0..102252b2f 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -3,7 +3,6 @@ # mypy: disallow_untyped_calls=True import argparse -import code import collections import logging import sys From 741015e1c786999f66fd49af08bf28419a5ab478 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 23:50:22 +0200 Subject: [PATCH 1431/1650] Fix default argument --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c2b502fe6..8cf1467d5 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -544,7 +544,7 @@ def request_reload(self, files_modified=()): if self.watching_files: self._request_reload(files_modified=files_modified) - def schedule_refresh(self, when="now"): + def schedule_refresh(self, when: float = 0) -> None: """Schedule a ScheduledRefreshRequestEvent for when. Such a event should interrupt if blockied waiting for keyboard input""" From 44c7d42fb8a6fda96529f454715ae7628e54e5c8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 May 2022 23:59:52 +0200 Subject: [PATCH 1432/1650] Turn search modes into an Enum --- bpython/curtsiesfrontend/repl.py | 40 +++++++++++++++++++------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 8cf1467d5..258a937b1 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -84,6 +84,12 @@ MAX_EVENTS_POSSIBLY_NOT_PASTE = 20 +class SearchMode(Enum): + NO_SEARCH = 0 + INCREMENTAL_SEARCH = 1 + REVERSE_INCREMENTAL_SEARCH = 2 + + class LineType(Enum): """Used when adding a tuple to all_logical_lines, to get input / output values having to actually type/know the strings""" @@ -452,9 +458,7 @@ def __init__( # whether auto reloading active self.watching_files = config.default_autoreload - # 'reverse_incremental_search', 'incremental_search' or None - self.incr_search_mode = None - + self.incr_search_mode = SearchMode.NO_SEARCH self.incr_search_target = "" self.original_modules = set(sys.modules.keys()) @@ -758,7 +762,7 @@ def process_key_event(self, e: str) -> None: self.incremental_search() elif ( e in (("",) + key_dispatch[self.config.backspace_key]) - and self.incr_search_mode + and self.incr_search_mode != SearchMode.NO_SEARCH ): self.add_to_incremental_search(self, backspace=True) elif e in self.edit_keys.cut_buffer_edits: @@ -808,7 +812,7 @@ def process_key_event(self, e: str) -> None: elif e in key_dispatch[self.config.edit_current_block_key]: self.send_current_block_to_external_editor() elif e in ("",): - self.incr_search_mode = None + self.incr_search_mode = SearchMode.NO_SEARCH elif e in ("",): self.add_normal_character(" ") elif e in CHARACTER_PAIR_MAP.keys(): @@ -852,7 +856,11 @@ def insert_char_pair_start(self, e): if not can_lookup_next else self._current_line[self._cursor_offset] ) - if start_of_line or end_of_line or next_char in "})] ": + if ( + start_of_line + or end_of_line + or (next_char is not None and next_char in "})] ") + ): self.add_normal_character( CHARACTER_PAIR_MAP[e], narrow_search=False ) @@ -891,7 +899,7 @@ def get_last_word(self): ) def incremental_search(self, reverse=False, include_current=False): - if self.incr_search_mode is None: + if self.incr_search_mode == SearchMode.NO_SEARCH: self.rl_history.enter(self.current_line) self.incr_search_target = "" else: @@ -920,9 +928,9 @@ def incremental_search(self, reverse=False, include_current=False): clear_special_mode=False, ) if reverse: - self.incr_search_mode = "reverse_incremental_search" + self.incr_search_mode = SearchMode.REVERSE_INCREMENTAL_SEARCH else: - self.incr_search_mode = "incremental_search" + self.incr_search_mode = SearchMode.INCREMENTAL_SEARCH def readline_kill(self, e): func = self.edit_keys[e] @@ -1172,7 +1180,7 @@ def toggle_file_watch(self): def add_normal_character(self, char, narrow_search=True): if len(char) > 1 or is_nop(char): return - if self.incr_search_mode: + if self.incr_search_mode != SearchMode.NO_SEARCH: self.add_to_incremental_search(char) else: self._set_current_line( @@ -1209,9 +1217,9 @@ def add_to_incremental_search(self, char=None, backspace=False): self.incr_search_target = self.incr_search_target[:-1] else: self.incr_search_target += char - if self.incr_search_mode == "reverse_incremental_search": + if self.incr_search_mode == SearchMode.REVERSE_INCREMENTAL_SEARCH: self.incremental_search(reverse=True, include_current=True) - elif self.incr_search_mode == "incremental_search": + elif self.incr_search_mode == SearchMode.INCREMENTAL_SEARCH: self.incremental_search(include_current=True) else: raise ValueError("add_to_incremental_search not in a special mode") @@ -1419,7 +1427,7 @@ def current_line_formatted(self): fs = bpythonparse( pygformat(self.tokenize(self.current_line), self.formatter) ) - if self.incr_search_mode: + if self.incr_search_mode != SearchMode.NO_SEARCH: if self.incr_search_target in self.current_line: fs = fmtfuncs.on_magenta(self.incr_search_target).join( fs.split(self.incr_search_target) @@ -1467,12 +1475,12 @@ def display_line_with_prompt(self): """colored line with prompt""" prompt = func_for_letter(self.config.color_scheme["prompt"]) more = func_for_letter(self.config.color_scheme["prompt_more"]) - if self.incr_search_mode == "reverse_incremental_search": + if self.incr_search_mode == SearchMode.REVERSE_INCREMENTAL_SEARCH: return ( prompt(f"(reverse-i-search)`{self.incr_search_target}': ") + self.current_line_formatted ) - elif self.incr_search_mode == "incremental_search": + elif self.incr_search_mode == SearchMode.INCREMENTAL_SEARCH: return prompt(f"(i-search)`%s': ") + self.current_line_formatted return ( prompt(self.ps1) if self.done else more(self.ps2) @@ -1905,7 +1913,7 @@ def _set_cursor_offset( if reset_rl_history: self.rl_history.reset() if clear_special_mode: - self.incr_search_mode = None + self.incr_search_mode = SearchMode.NO_SEARCH self._cursor_offset = offset if update_completion: self.update_completion() From 49255d6bfcc69465fe1a2c5d82d015a2fdf4488f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 00:10:13 +0200 Subject: [PATCH 1433/1650] Re-arrange checks --- bpython/curtsiesfrontend/repl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 258a937b1..98b78d05c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1211,12 +1211,12 @@ def add_to_incremental_search(self, char=None, backspace=False): The only operations allowed in incremental search mode are adding characters and backspacing.""" - if char is None and not backspace: - raise ValueError("must provide a char or set backspace to True") if backspace: self.incr_search_target = self.incr_search_target[:-1] - else: + elif char is not None: self.incr_search_target += char + else: + raise ValueError("must provide a char or set backspace to True") if self.incr_search_mode == SearchMode.REVERSE_INCREMENTAL_SEARCH: self.incremental_search(reverse=True, include_current=True) elif self.incr_search_mode == SearchMode.INCREMENTAL_SEARCH: From 2776156c535368e723582ca1835efd6881e7e8e1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 00:21:35 +0200 Subject: [PATCH 1434/1650] Directly sum up lengths There is no need to build the complete string. --- bpython/curtsiesfrontend/repl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 98b78d05c..7020b678b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1536,15 +1536,15 @@ def number_of_padding_chars_on_current_cursor_line(self): Should return zero unless there are fullwidth characters.""" full_line = self.current_cursor_line_without_suggestion - line_with_padding = "".join( - line.s + line_with_padding_len = sum( + len(line.s) for line in paint.display_linize( self.current_cursor_line_without_suggestion.s, self.width ) ) # the difference in length here is how much padding there is - return len(line_with_padding) - len(full_line) + return line_with_padding_len - len(full_line) def paint( self, From 2c80e03655c79527f05abb5a2e12a733bd50d11a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 00:52:30 +0200 Subject: [PATCH 1435/1650] Add type annotations --- bpython/curtsiesfrontend/preprocess.py | 35 +++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index e0d15f4ec..5e59dd499 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -1,34 +1,38 @@ """Tools for preparing code to be run in the REPL (removing blank lines, etc)""" -from ..lazyre import LazyReCompile +from codeop import CommandCompiler +from typing import Match from itertools import tee, islice, chain +from ..lazyre import LazyReCompile + # TODO specifically catch IndentationErrors instead of any syntax errors indent_empty_lines_re = LazyReCompile(r"\s*") tabs_to_spaces_re = LazyReCompile(r"^\t+") -def indent_empty_lines(s, compiler): +def indent_empty_lines(s: str, compiler: CommandCompiler) -> str: """Indents blank lines that would otherwise cause early compilation Only really works if starting on a new line""" - lines = s.split("\n") + initial_lines = s.split("\n") ends_with_newline = False - if lines and not lines[-1]: + if initial_lines and not initial_lines[-1]: ends_with_newline = True - lines.pop() + initial_lines.pop() result_lines = [] - prevs, lines, nexts = tee(lines, 3) + prevs, lines, nexts = tee(initial_lines, 3) prevs = chain(("",), prevs) nexts = chain(islice(nexts, 1, None), ("",)) for p_line, line, n_line in zip(prevs, lines, nexts): if len(line) == 0: - p_indent = indent_empty_lines_re.match(p_line).group() - n_indent = indent_empty_lines_re.match(n_line).group() + # "\s*" always matches + p_indent = indent_empty_lines_re.match(p_line).group() # type: ignore + n_indent = indent_empty_lines_re.match(n_line).group() # type: ignore result_lines.append(min([p_indent, n_indent], key=len) + line) else: result_lines.append(line) @@ -36,17 +40,14 @@ def indent_empty_lines(s, compiler): return "\n".join(result_lines) + ("\n" if ends_with_newline else "") -def leading_tabs_to_spaces(s): - lines = s.split("\n") - result_lines = [] - - def tab_to_space(m): +def leading_tabs_to_spaces(s: str) -> str: + def tab_to_space(m: Match[str]) -> str: return len(m.group()) * 4 * " " - for line in lines: - result_lines.append(tabs_to_spaces_re.sub(tab_to_space, line)) - return "\n".join(result_lines) + return "\n".join( + tabs_to_spaces_re.sub(tab_to_space, line) for line in s.split("\n") + ) -def preprocess(s, compiler): +def preprocess(s: str, compiler: CommandCompiler) -> str: return indent_empty_lines(leading_tabs_to_spaces(s), compiler) From 9ffa5f3eef565fed62a7c7976ec856970249fd14 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 01:13:50 +0200 Subject: [PATCH 1436/1650] Add type annotations --- bpython/curtsiesfrontend/filewatch.py | 25 +++++++++++++------------ stubs/watchdog/events.pyi | 4 ++++ stubs/watchdog/observers.pyi | 6 +++++- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 7616d4845..8f64fd3db 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -1,11 +1,12 @@ import os from collections import defaultdict +from typing import Dict, Iterable, Set, List from .. import importcompletion try: from watchdog.observers import Observer - from watchdog.events import FileSystemEventHandler + from watchdog.events import FileSystemEventHandler, FileSystemEvent except ImportError: def ModuleChangedEventHandler(*args): @@ -14,12 +15,12 @@ def ModuleChangedEventHandler(*args): else: class ModuleChangedEventHandler(FileSystemEventHandler): # type: ignore [no-redef] - def __init__(self, paths, on_change): - self.dirs = defaultdict(set) + def __init__(self, paths: Iterable[str], on_change) -> None: + self.dirs: Dict[str, Set[str]] = defaultdict(set) self.on_change = on_change - self.modules_to_add_later = [] + self.modules_to_add_later: List[str] = [] self.observer = Observer() - self.old_dirs = defaultdict(set) + self.old_dirs: Dict[str, Set[str]] = defaultdict(set) self.started = False self.activated = False for path in paths: @@ -27,13 +28,13 @@ def __init__(self, paths, on_change): super().__init__() - def reset(self): + def reset(self) -> None: self.dirs = defaultdict(set) del self.modules_to_add_later[:] self.old_dirs = defaultdict(set) self.observer.unschedule_all() - def _add_module(self, path): + def _add_module(self, path: str) -> None: """Add a python module to track changes""" path = os.path.abspath(path) for suff in importcompletion.SUFFIXES: @@ -45,10 +46,10 @@ def _add_module(self, path): self.observer.schedule(self, dirname, recursive=False) self.dirs[dirname].add(path) - def _add_module_later(self, path): + def _add_module_later(self, path: str) -> None: self.modules_to_add_later.append(path) - def track_module(self, path): + def track_module(self, path: str) -> None: """ Begins tracking this if activated, or remembers to track later. """ @@ -57,7 +58,7 @@ def track_module(self, path): else: self._add_module_later(path) - def activate(self): + def activate(self) -> None: if self.activated: raise ValueError(f"{self!r} is already activated.") if not self.started: @@ -70,13 +71,13 @@ def activate(self): del self.modules_to_add_later[:] self.activated = True - def deactivate(self): + def deactivate(self) -> None: if not self.activated: raise ValueError(f"{self!r} is not activated.") self.observer.unschedule_all() self.activated = False - def on_any_event(self, event): + def on_any_event(self, event: FileSystemEvent) -> None: dirpath = os.path.dirname(event.src_path) paths = [path + ".py" for path in self.dirs[dirpath]] if event.src_path in paths: diff --git a/stubs/watchdog/events.pyi b/stubs/watchdog/events.pyi index 6e17bd6df..ded1fe942 100644 --- a/stubs/watchdog/events.pyi +++ b/stubs/watchdog/events.pyi @@ -1 +1,5 @@ +class FileSystemEvent: + @property + def src_path(self) -> str: ... + class FileSystemEventHandler: ... diff --git a/stubs/watchdog/observers.pyi b/stubs/watchdog/observers.pyi index 7db3099fb..c4596f2d9 100644 --- a/stubs/watchdog/observers.pyi +++ b/stubs/watchdog/observers.pyi @@ -1,4 +1,8 @@ +from .events import FileSystemEventHandler + class Observer: def start(self): ... - def schedule(self, dirname: str, recursive: bool): ... + def schedule( + self, observer: FileSystemEventHandler, dirname: str, recursive: bool + ): ... def unschedule_all(self): ... From 43778e8dc955ca0163fc846eaa0400dee096b9e9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 01:21:54 +0200 Subject: [PATCH 1437/1650] Remove unused member --- bpython/curtsiesfrontend/filewatch.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 8f64fd3db..33054c4cc 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -20,7 +20,6 @@ def __init__(self, paths: Iterable[str], on_change) -> None: self.on_change = on_change self.modules_to_add_later: List[str] = [] self.observer = Observer() - self.old_dirs: Dict[str, Set[str]] = defaultdict(set) self.started = False self.activated = False for path in paths: @@ -31,7 +30,6 @@ def __init__(self, paths: Iterable[str], on_change) -> None: def reset(self) -> None: self.dirs = defaultdict(set) del self.modules_to_add_later[:] - self.old_dirs = defaultdict(set) self.observer.unschedule_all() def _add_module(self, path: str) -> None: From 5f070f474e5a3a0761c29621a72829edf32d6734 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 09:06:08 +0200 Subject: [PATCH 1438/1650] Initialize width/height using os.get_terminal_size --- bpython/curtsiesfrontend/repl.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 7020b678b..8ba7545da 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -19,7 +19,6 @@ Optional, Tuple, Union, - cast, Type, ) from .._typing_compat import Literal @@ -465,8 +464,7 @@ def __init__( # as long as the first event received is a window resize event, # this works fine... - self.width: int = cast(int, None) - self.height: int = cast(int, None) + self.width, self.height = os.get_terminal_size() self.status_bar.message(banner) From e82b4fc5279974598a1c0650ce9b2df35333df3a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 09:29:37 +0200 Subject: [PATCH 1439/1650] Handle OSError from unit tests --- bpython/curtsiesfrontend/repl.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 8ba7545da..2ea4269cf 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -464,7 +464,12 @@ def __init__( # as long as the first event received is a window resize event, # this works fine... - self.width, self.height = os.get_terminal_size() + try: + self.width, self.height = os.get_terminal_size() + except OSError: + # this case will trigger during unit tests when stdout is redirected + self.width = -1 + self.height = -1 self.status_bar.message(banner) From 592725a487a9da0fd5523676a99993314a974d8e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 09:35:32 +0200 Subject: [PATCH 1440/1650] Improve on_any_event and add type annotations --- bpython/curtsies.py | 2 +- bpython/curtsiesfrontend/filewatch.py | 15 ++++++++++----- bpython/curtsiesfrontend/repl.py | 7 ++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 102252b2f..caa6e08e7 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -104,7 +104,7 @@ def _request_refresh(self) -> None: def _schedule_refresh(self, when: float) -> None: return self._schedule_refresh_callback(when) - def _request_reload(self, files_modified: Sequence[str] = ("?",)) -> None: + def _request_reload(self, files_modified: Sequence[str]) -> None: return self._request_reload_callback(files_modified) def interrupting_refresh(self) -> None: diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 33054c4cc..8933e2930 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -1,6 +1,6 @@ import os from collections import defaultdict -from typing import Dict, Iterable, Set, List +from typing import Callable, Dict, Iterable, Sequence, Set, List from .. import importcompletion @@ -15,7 +15,11 @@ def ModuleChangedEventHandler(*args): else: class ModuleChangedEventHandler(FileSystemEventHandler): # type: ignore [no-redef] - def __init__(self, paths: Iterable[str], on_change) -> None: + def __init__( + self, + paths: Iterable[str], + on_change: Callable[[Sequence[str]], None], + ) -> None: self.dirs: Dict[str, Set[str]] = defaultdict(set) self.on_change = on_change self.modules_to_add_later: List[str] = [] @@ -77,6 +81,7 @@ def deactivate(self) -> None: def on_any_event(self, event: FileSystemEvent) -> None: dirpath = os.path.dirname(event.src_path) - paths = [path + ".py" for path in self.dirs[dirpath]] - if event.src_path in paths: - self.on_change(files_modified=[event.src_path]) + if any( + event.src_path == f"{path}.py" for path in self.dirs[dirpath] + ): + self.on_change((event.src_path,)) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2ea4269cf..2226cae32 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -17,6 +17,7 @@ Any, List, Optional, + Sequence, Tuple, Union, Type, @@ -516,7 +517,7 @@ def _request_refresh(self): RefreshRequestEvent.""" raise NotImplementedError - def _request_reload(self, files_modified=("?",)): + def _request_reload(self, files_modified: Sequence[str]) -> None: """Like request_refresh, but for reload requests events.""" raise NotImplementedError @@ -546,10 +547,10 @@ def request_refresh(self): else: self._request_refresh() - def request_reload(self, files_modified=()): + def request_reload(self, files_modified: Sequence[str] = ()) -> None: """Request that a ReloadEvent be passed next into process_event""" if self.watching_files: - self._request_reload(files_modified=files_modified) + self._request_reload(files_modified) def schedule_refresh(self, when: float = 0) -> None: """Schedule a ScheduledRefreshRequestEvent for when. From 56dfd88d11212a1834222a52a981b4a7bb0713f1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 09:36:13 +0200 Subject: [PATCH 1441/1650] Ignore mypy cache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1bda0f251..7a81cbfe2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ doc/sphinx/build/* bpython/_version.py venv/ .venv/ +.mypy_cache/ From e987ad32baf45f0bb89e4a2fd3f9f6d36abfd1d6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 09:36:45 +0200 Subject: [PATCH 1442/1650] Add type annotations --- bpython/curtsiesfrontend/repl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2226cae32..7a591d416 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -11,7 +11,7 @@ import time import unicodedata from enum import Enum -from types import TracebackType +from types import FrameType, TracebackType from typing import ( Dict, Any, @@ -611,7 +611,7 @@ def __exit__( sys.meta_path = self.orig_meta_path return False - def sigwinch_handler(self, signum, frame): + def sigwinch_handler(self, signum: int, frame: Optional[FrameType]) -> None: old_rows, old_columns = self.height, self.width self.height, self.width = self.get_term_hw() cursor_dy = self.get_cursor_vertical_diff() @@ -627,7 +627,7 @@ def sigwinch_handler(self, signum, frame): self.scroll_offset, ) - def sigtstp_handler(self, signum, frame): + def sigtstp_handler(self, signum: int, frame: Optional[FrameType]) -> None: self.scroll_offset = len(self.lines_for_display) self.__exit__(None, None, None) self.on_suspend() From 28ea475bbafa85864c476a129725b3bf4d006cf5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 23:18:52 +0200 Subject: [PATCH 1443/1650] Store window to avoid implicit dependency on blessing/blessed --- bpython/curtsies.py | 3 ++- bpython/curtsiesfrontend/repl.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index caa6e08e7..f7b2ef472 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -61,7 +61,7 @@ def __init__( self.input_generator = curtsies.input.Input( keynames="curtsies", sigint_event=True, paste_threshold=None ) - self.window = curtsies.window.CursorAwareWindow( + window = curtsies.window.CursorAwareWindow( sys.stdout, sys.stdin, keep_last_line=True, @@ -92,6 +92,7 @@ def __init__( super().__init__( config, + window, locals_=locals_, banner=banner, interp=interp, diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 7a591d416..3a44f4e5b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -24,7 +24,6 @@ ) from .._typing_compat import Literal -import blessings import greenlet from curtsies import ( FSArray, @@ -37,6 +36,7 @@ ) from curtsies.configfile_keynames import keymap as key_dispatch from curtsies.input import is_main_thread +from curtsies.window import BaseWindow from cwcwidth import wcswidth from pygments import format as pygformat from pygments.formatters import TerminalFormatter @@ -326,6 +326,7 @@ class BaseRepl(Repl): def __init__( self, config: Config, + window: BaseWindow, locals_: Optional[Dict[str, Any]] = None, banner: Optional[str] = None, interp: Optional[Interp] = None, @@ -340,6 +341,7 @@ def __init__( """ logger.debug("starting init") + self.window = window # If creating a new interpreter on undo would be unsafe because initial # state was passed in @@ -2077,7 +2079,7 @@ def focus_on_subprocess(self, args): try: signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) with Termmode(self.orig_stdin, self.orig_tcattrs): - terminal = blessings.Terminal(stream=sys.__stdout__) + terminal = self.window.t with terminal.fullscreen(): sys.__stdout__.write(terminal.save) sys.__stdout__.write(terminal.move(0, 0)) From 0d16fe6e3a896a4fa759e1cb08605b0c7bf25c83 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 23:32:56 +0200 Subject: [PATCH 1444/1650] Add type annotations --- bpython/curtsiesfrontend/repl.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3a44f4e5b..b5956ba7b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -388,7 +388,7 @@ def __init__( self._current_line = "" # current line of output - stdout and stdin go here - self.current_stdouterr_line = "" # Union[str, FmtStr] + self.current_stdouterr_line: Union[str, FmtStr] = "" # this is every line that's been displayed (input and output) # as with formatting applied. Logical lines that exceeded the terminal width @@ -657,8 +657,7 @@ def process_event(self, e: Union[events.Event, str]) -> Optional[bool]: self.process_key_event(e) return None - def process_control_event(self, e) -> Optional[bool]: - + def process_control_event(self, e: events.Event) -> Optional[bool]: if isinstance(e, bpythonevents.ScheduledRefreshRequestEvent): # This is a scheduled refresh - it's really just a refresh (so nop) pass @@ -703,9 +702,9 @@ def process_control_event(self, e) -> Optional[bool]: elif isinstance(e, bpythonevents.RunStartupFileEvent): try: self.startup() - except OSError as e: + except OSError as err: self.status_bar.message( - _("Executing PYTHONSTARTUP failed: %s") % (e,) + _("Executing PYTHONSTARTUP failed: %s") % (err,) ) elif isinstance(e, bpythonevents.UndoEvent): From 80bd3e0ef05ac40abe98aa9f3b84db1e25687c28 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 23:47:30 +0200 Subject: [PATCH 1445/1650] Make window optional for tests Where required, assert that window is not None. --- bpython/curtsiesfrontend/repl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index b5956ba7b..2c8e93e1f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -326,7 +326,7 @@ class BaseRepl(Repl): def __init__( self, config: Config, - window: BaseWindow, + window: Optional[BaseWindow] = None, locals_: Optional[Dict[str, Any]] = None, banner: Optional[str] = None, interp: Optional[Interp] = None, @@ -2078,6 +2078,7 @@ def focus_on_subprocess(self, args): try: signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) with Termmode(self.orig_stdin, self.orig_tcattrs): + assert self.window is not None terminal = self.window.t with terminal.fullscreen(): sys.__stdout__.write(terminal.save) From 8d16a71ef404db66d2c6fae6c362640da8ae240d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 May 2022 00:02:30 +0200 Subject: [PATCH 1446/1650] Change the type to CursorAwareWindow Pass None in the tests. This is good enough. --- bpython/curtsiesfrontend/repl.py | 4 ++-- bpython/test/test_brackets_completion.py | 7 ++++++- bpython/test/test_curtsies_painting.py | 8 ++++++-- bpython/test/test_curtsies_repl.py | 6 +++++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2c8e93e1f..49d61a84b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -36,7 +36,7 @@ ) from curtsies.configfile_keynames import keymap as key_dispatch from curtsies.input import is_main_thread -from curtsies.window import BaseWindow +from curtsies.window import CursorAwareWindow from cwcwidth import wcswidth from pygments import format as pygformat from pygments.formatters import TerminalFormatter @@ -326,7 +326,7 @@ class BaseRepl(Repl): def __init__( self, config: Config, - window: Optional[BaseWindow] = None, + window: CursorAwareWindow, locals_: Optional[Dict[str, Any]] = None, banner: Optional[str] = None, interp: Optional[Interp] = None, diff --git a/bpython/test/test_brackets_completion.py b/bpython/test/test_brackets_completion.py index fd9836650..4340ad3d0 100644 --- a/bpython/test/test_brackets_completion.py +++ b/bpython/test/test_brackets_completion.py @@ -1,9 +1,12 @@ import os +from typing import cast from bpython.test import FixLanguageTestCase as TestCase, TEST_CONFIG from bpython.curtsiesfrontend import repl as curtsiesrepl from bpython import config +from curtsies.window import CursorAwareWindow + def setup_config(conf): config_struct = config.Config(TEST_CONFIG) @@ -18,7 +21,9 @@ def create_repl(brackets_enabled=False, **kwargs): config = setup_config( {"editor": "true", "brackets_completion": brackets_enabled} ) - repl = curtsiesrepl.BaseRepl(config, **kwargs) + repl = curtsiesrepl.BaseRepl( + config, cast(None, CursorAwareWindow), **kwargs + ) os.environ["PAGER"] = "true" os.environ.pop("PYTHONSTARTUP", None) repl.width = 50 diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 9f98bf066..813b4fb3d 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -5,12 +5,14 @@ import sys from contextlib import contextmanager +from typing import cast from curtsies.formatstringarray import ( fsarray, assertFSArraysEqual, assertFSArraysEqualIgnoringFormatting, ) from curtsies.fmtfuncs import cyan, bold, green, yellow, on_magenta, red +from curtsies.window import CursorAwareWindow from unittest import mock from bpython.curtsiesfrontend.events import RefreshRequestEvent @@ -56,7 +58,7 @@ class TestRepl(BaseRepl): def _request_refresh(inner_self): pass - self.repl = TestRepl(config=setup_config()) + self.repl = TestRepl(setup_config(), cast(None, CursorAwareWindow)) self.repl.height, self.repl.width = (5, 10) @property @@ -284,7 +286,9 @@ class TestRepl(BaseRepl): def _request_refresh(inner_self): self.refresh() - self.repl = TestRepl(banner="", config=setup_config()) + self.repl = TestRepl( + setup_config(), cast(None, CursorAwareWindow), banner="" + ) self.repl.height, self.repl.width = (5, 32) def send_key(self, key): diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index a6e4c7866..5a19c6abb 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -3,6 +3,7 @@ import sys import tempfile import io +from typing import cast import unittest from contextlib import contextmanager @@ -23,6 +24,7 @@ ) from curtsies import events +from curtsies.window import CursorAwareWindow from importlib import invalidate_caches @@ -231,7 +233,9 @@ def captured_output(): def create_repl(**kwargs): config = setup_config({"editor": "true"}) - repl = curtsiesrepl.BaseRepl(config, **kwargs) + repl = curtsiesrepl.BaseRepl( + config, cast(CursorAwareWindow, None), **kwargs + ) os.environ["PAGER"] = "true" os.environ.pop("PYTHONSTARTUP", None) repl.width = 50 From 995747d64d03291c0180bf8c6932a96d7541696a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 May 2022 00:07:56 +0200 Subject: [PATCH 1447/1650] Fix tests --- bpython/test/test_brackets_completion.py | 2 +- bpython/test/test_curtsies_painting.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/test/test_brackets_completion.py b/bpython/test/test_brackets_completion.py index 4340ad3d0..14169d6a8 100644 --- a/bpython/test/test_brackets_completion.py +++ b/bpython/test/test_brackets_completion.py @@ -22,7 +22,7 @@ def create_repl(brackets_enabled=False, **kwargs): {"editor": "true", "brackets_completion": brackets_enabled} ) repl = curtsiesrepl.BaseRepl( - config, cast(None, CursorAwareWindow), **kwargs + config, cast(CursorAwareWindow, None), **kwargs ) os.environ["PAGER"] = "true" os.environ.pop("PYTHONSTARTUP", None) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 813b4fb3d..2804643ce 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -58,7 +58,7 @@ class TestRepl(BaseRepl): def _request_refresh(inner_self): pass - self.repl = TestRepl(setup_config(), cast(None, CursorAwareWindow)) + self.repl = TestRepl(setup_config(), cast(CursorAwareWindow, None)) self.repl.height, self.repl.width = (5, 10) @property @@ -287,7 +287,7 @@ def _request_refresh(inner_self): self.refresh() self.repl = TestRepl( - setup_config(), cast(None, CursorAwareWindow), banner="" + setup_config(), cast(CursorAwareWindow, None), banner="" ) self.repl.height, self.repl.width = (5, 32) From 7efed26f3bd0a5f959c96d8081a464c7d42c0fdc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 11:53:23 +0200 Subject: [PATCH 1448/1650] Add type annotations --- bpython/curtsiesfrontend/repl.py | 61 ++++++++++++++++---------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 49d61a84b..91e55b708 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -15,6 +15,7 @@ from typing import ( Dict, Any, + Iterable, List, Optional, Sequence, @@ -161,7 +162,7 @@ def process_event(self, e: Union[events.Event, str]) -> None: else: self.repl.send_to_stdin(self.current_line) - def add_input_character(self, e): + def add_input_character(self, e: str) -> None: if e in ("",): e = " " if e.startswith("<") and e.endswith(">"): @@ -190,10 +191,10 @@ def readlines(self, size=-1): def __iter__(self): return iter(self.readlines()) - def isatty(self): + def isatty(self) -> bool: return True - def flush(self): + def flush(self) -> None: """Flush the internal buffer. This is a no-op. Flushing stdin doesn't make any sense anyway.""" @@ -202,7 +203,7 @@ def write(self, value): # others, so here's a hack to keep them happy raise OSError(errno.EBADF, "sys.stdin is read-only") - def close(self): + def close(self) -> None: # hack to make closing stdin a nop # This is useful for multiprocessing.Process, which does work # for the most part, although output from other processes is @@ -210,7 +211,7 @@ def close(self): pass @property - def encoding(self): + def encoding(self) -> str: return sys.__stdin__.encoding # TODO write a read() method? @@ -1083,7 +1084,7 @@ def down_one_line(self): ) self._set_cursor_offset(len(self.current_line), reset_rl_history=False) - def process_simple_keypress(self, e): + def process_simple_keypress(self, e: str): # '\n' needed for pastes if e in ("", "", "", "\n", "\r"): self.on_enter() @@ -1980,7 +1981,7 @@ def prompt_for_undo(): greenlet.greenlet(prompt_for_undo).switch() - def redo(self): + def redo(self) -> None: if self.redo_stack: temp = self.redo_stack.pop() self.history.append(temp) @@ -2061,7 +2062,7 @@ def initialize_interp(self) -> None: del self.coderunner.interp.locals["_repl"] - def getstdout(self): + def getstdout(self) -> str: """ Returns a string of the current bpython session, wrapped, WITH prompts. """ @@ -2096,7 +2097,7 @@ def focus_on_subprocess(self, args): finally: signal.signal(signal.SIGWINCH, prev_sigwinch_handler) - def pager(self, text): + def pager(self, text: str) -> None: """Runs an external pager on text text must be a str""" @@ -2106,7 +2107,7 @@ def pager(self, text): tmp.flush() self.focus_on_subprocess(command + [tmp.name]) - def show_source(self): + def show_source(self) -> None: try: source = self.get_source_of_current_name() except SourceNotFound as e: @@ -2118,10 +2119,10 @@ def show_source(self): ) self.pager(source) - def help_text(self): + def help_text(self) -> str: return self.version_help_text() + "\n" + self.key_help_text() - def version_help_text(self): + def version_help_text(self) -> str: help_message = _( """ Thanks for using bpython! @@ -2148,7 +2149,7 @@ def version_help_text(self): return f"bpython-curtsies version {__version__} using curtsies version {curtsies_version}\n{help_message}" - def key_help_text(self): + def key_help_text(self) -> str: NOT_IMPLEMENTED = ( "suspend", "cut to buffer", @@ -2198,15 +2199,15 @@ def ps2(self): return _process_ps(super().ps2, "... ") -def is_nop(char): - return unicodedata.category(str(char)) == "Cc" +def is_nop(char: str) -> bool: + return unicodedata.category(char) == "Cc" -def tabs_to_spaces(line): +def tabs_to_spaces(line: str) -> str: return line.replace("\t", " ") -def _last_word(line): +def _last_word(line: str) -> str: split_line = line.split() return split_line.pop() if split_line else "" @@ -2230,29 +2231,29 @@ def compress_paste_event(paste_event): return None -def just_simple_events(event_list): +def just_simple_events( + event_list: Iterable[Union[str, events.Event]] +) -> List[str]: simple_events = [] for e in event_list: + if isinstance(e, events.Event): + continue # ignore events # '\n' necessary for pastes - if e in ("", "", "", "\n", "\r"): + elif e in ("", "", "", "\n", "\r"): simple_events.append("\n") - elif isinstance(e, events.Event): - pass # ignore events - elif e in ("",): + elif e == "": simple_events.append(" ") elif len(e) > 1: - pass # get rid of etc. + continue # get rid of etc. else: simple_events.append(e) return simple_events -def is_simple_event(e): +def is_simple_event(e: Union[str, events.Event]) -> bool: if isinstance(e, events.Event): return False - if e in ("", "", "", "\n", "\r", ""): - return True - if len(e) > 1: - return False - else: - return True + return ( + e in ("", "", "", "\n", "\r", "") + or len(e) <= 1 + ) From 2df70ac1eab0b17d2ec62e19b5e64fe610a19fb6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 5 May 2022 11:53:53 +0200 Subject: [PATCH 1449/1650] Simplify some checks --- bpython/curtsiesfrontend/repl.py | 83 ++++++++++++++++---------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 91e55b708..d5a3eed15 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -122,40 +122,42 @@ def process_event(self, e: Union[events.Event, str]) -> None: assert self.has_focus logger.debug("fake input processing event %r", e) - if isinstance(e, events.PasteEvent): - for ee in e.events: - if ee not in self.rl_char_sequences: - self.add_input_character(ee) - elif e in self.rl_char_sequences: - self.cursor_offset, self.current_line = self.rl_char_sequences[e]( - self.cursor_offset, self.current_line - ) - elif isinstance(e, events.SigIntEvent): - self.coderunner.sigint_happened_in_main_context = True - self.has_focus = False - self.current_line = "" - self.cursor_offset = 0 - self.repl.run_code_and_maybe_finish() - elif e in ("",): - pass - elif e in ("",): - if self.current_line == "": - self.repl.send_to_stdin("\n") + if isinstance(e, events.Event): + if isinstance(e, events.PasteEvent): + for ee in e.events: + if ee not in self.rl_char_sequences: + self.add_input_character(ee) + elif isinstance(e, events.SigIntEvent): + self.coderunner.sigint_happened_in_main_context = True self.has_focus = False self.current_line = "" self.cursor_offset = 0 - self.repl.run_code_and_maybe_finish(for_code="") - else: + self.repl.run_code_and_maybe_finish() + else: + if e in self.rl_char_sequences: + self.cursor_offset, self.current_line = self.rl_char_sequences[ + e + ](self.cursor_offset, self.current_line) + elif e in ("",): pass - elif e in ("\n", "\r", "", ""): - line = self.current_line - self.repl.send_to_stdin(line + "\n") - self.has_focus = False - self.current_line = "" - self.cursor_offset = 0 - self.repl.run_code_and_maybe_finish(for_code=line + "\n") - else: # add normal character - self.add_input_character(e) + elif e in ("",): + if self.current_line == "": + self.repl.send_to_stdin("\n") + self.has_focus = False + self.current_line = "" + self.cursor_offset = 0 + self.repl.run_code_and_maybe_finish(for_code="") + else: + pass + elif e in ("\n", "\r", "", ""): + line = self.current_line + self.repl.send_to_stdin(line + "\n") + self.has_focus = False + self.current_line = "" + self.cursor_offset = 0 + self.repl.run_code_and_maybe_finish(for_code=line + "\n") + else: # add normal character + self.add_input_character(e) if self.current_line.endswith(("\n", "\r")): pass @@ -163,17 +165,16 @@ def process_event(self, e: Union[events.Event, str]) -> None: self.repl.send_to_stdin(self.current_line) def add_input_character(self, e: str) -> None: - if e in ("",): + if e == "": e = " " if e.startswith("<") and e.endswith(">"): return assert len(e) == 1, "added multiple characters: %r" % e logger.debug("adding normal char %r to current line", e) - c = e self.current_line = ( self.current_line[: self.cursor_offset] - + c + + e + self.current_line[self.cursor_offset :] ) self.cursor_offset += 1 @@ -756,11 +757,11 @@ def process_key_event(self, e: str) -> None: self.up_one_line() elif e in ("",) + key_dispatch[self.config.down_one_line_key]: self.down_one_line() - elif e in ("",): + elif e == "": self.on_control_d() - elif e in ("",): + elif e == "": self.operate_and_get_next() - elif e in ("",): + elif e == "": self.get_last_word() elif e in key_dispatch[self.config.reverse_incremental_search_key]: self.incremental_search(reverse=True) @@ -796,9 +797,9 @@ def process_key_event(self, e: str) -> None: raise SystemExit() elif e in ("\n", "\r", "", "", ""): self.on_enter() - elif e in ("",): # tab + elif e == "": # tab self.on_tab() - elif e in ("",): + elif e == "": self.on_tab(back=True) elif e in key_dispatch[self.config.undo_key]: # ctrl-r for undo self.prompt_undo() @@ -817,9 +818,9 @@ def process_key_event(self, e: str) -> None: # TODO add PAD keys hack as in bpython.cli elif e in key_dispatch[self.config.edit_current_block_key]: self.send_current_block_to_external_editor() - elif e in ("",): + elif e == "": self.incr_search_mode = SearchMode.NO_SEARCH - elif e in ("",): + elif e == "": self.add_normal_character(" ") elif e in CHARACTER_PAIR_MAP.keys(): if e in ["'", '"']: @@ -1093,7 +1094,7 @@ def process_simple_keypress(self, e: str): self.process_event(bpythonevents.RefreshRequestEvent()) elif isinstance(e, events.Event): pass # ignore events - elif e in ("",): + elif e == "": self.add_normal_character(" ") else: self.add_normal_character(e) From 7d3f3d961af070c0fb1f03dcb704ddd6b14e2440 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 May 2022 09:15:55 +0200 Subject: [PATCH 1450/1650] Remove unused import --- bpython/repl.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index e0d42b1f9..06934627e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -65,10 +65,6 @@ have_pyperclip = False from . import autocomplete, inspection, simpleeval - -if TYPE_CHECKING: - from .cli import Statusbar - from .config import getpreferredencoding, Config from .formatter import Parenthesis from .history import History From b3b74b8f49fcdee8d97ac881eca18e93f0b9621a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 May 2022 09:17:40 +0200 Subject: [PATCH 1451/1650] eval wants a dict, so make sure that the Interpreter has a dict --- bpython/cli.py | 2 +- bpython/repl.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 5383a7c38..2512d6ffe 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1940,7 +1940,7 @@ def main_curses( args: List[str], config: Config, interactive: bool = True, - locals_: Optional[MutableMapping[str, str]] = None, + locals_: Optional[Dict[str, Any]] = None, banner: Optional[str] = None, ) -> Tuple[Tuple[Any, ...], str]: """main function for the curses convenience wrapper diff --git a/bpython/repl.py b/bpython/repl.py index 06934627e..e41536fd0 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -48,7 +48,6 @@ Optional, Type, Union, - MutableMapping, Callable, Dict, TYPE_CHECKING, @@ -109,7 +108,7 @@ class Interpreter(code.InteractiveInterpreter): def __init__( self, - locals: Optional[MutableMapping[str, Any]] = None, + locals: Optional[Dict[str, Any]] = None, ) -> None: """Constructor. From dc667b6c9cc72117588559f30c89501781052efa Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 6 May 2022 14:17:38 +0200 Subject: [PATCH 1452/1650] Directly initialize f_strings --- bpython/curtsiesfrontend/interpreter.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 1b3133459..95e4b0691 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -44,9 +44,7 @@ class BPythonFormatter(Formatter): straightforward.""" def __init__(self, color_scheme, **options): - self.f_strings = {} - for k, v in color_scheme.items(): - self.f_strings[k] = f"\x01{v}" + self.f_strings = {k: f"\x01{v}" for k, v in color_scheme.items()} super().__init__(**options) def format(self, tokensource, outfile): From 492e3d5ff2643191f023e98b8959792ed4bfa0a3 Mon Sep 17 00:00:00 2001 From: rcreddyn Date: Sat, 15 Jan 2022 01:11:23 +0530 Subject: [PATCH 1453/1650] Added type annotations Sebastian: fixed some type annotations --- bpython/curtsiesfrontend/interpreter.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 95e4b0691..80b7d5f5b 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,11 +1,13 @@ import sys -from typing import Any, Dict, Optional +from codeop import CommandCompiler +from typing import Any, Dict, Iterable, Optional, Tuple, Union from pygments.token import Generic, Token, Keyword, Name, Comment, String from pygments.token import Error, Literal, Number, Operator, Punctuation -from pygments.token import Whitespace +from pygments.token import Whitespace, _TokenType from pygments.formatter import Formatter from pygments.lexers import get_lexer_by_name +from curtsies.formatstring import FmtStr from ..curtsiesfrontend.parse import parse from ..repl import Interpreter as ReplInterpreter @@ -43,7 +45,11 @@ class BPythonFormatter(Formatter): See the Pygments source for more info; it's pretty straightforward.""" - def __init__(self, color_scheme, **options): + def __init__( + self, + color_scheme: Dict[_TokenType, str], + **options: Union[str, bool, None], + ) -> None: self.f_strings = {k: f"\x01{v}" for k, v in color_scheme.items()} super().__init__(**options) @@ -71,7 +77,7 @@ def __init__( # typically changed after being instantiated # but used when interpreter used corresponding REPL - def write(err_line): + def write(err_line: Union[str, FmtStr]) -> None: """Default stderr handler for tracebacks Accepts FmtStrs so interpreters can output them""" @@ -80,13 +86,14 @@ def write(err_line): self.write = write # type: ignore self.outfile = self - def writetb(self, lines): + def writetb(self, lines: Iterable[str]) -> None: tbtext = "".join(lines) lexer = get_lexer_by_name("pytb") self.format(tbtext, lexer) # TODO for tracebacks get_lexer_by_name("pytb", stripall=True) - def format(self, tbtext, lexer): + def format(self, tbtext: str, lexer: Any) -> None: + # FIXME: lexer should is a Lexer traceback_informative_formatter = BPythonFormatter(default_colors) traceback_code_formatter = BPythonFormatter({Token: ("d")}) tokens = list(lexer.get_tokens(tbtext)) @@ -112,7 +119,9 @@ def format(self, tbtext, lexer): assert cur_line == [], cur_line -def code_finished_will_parse(s, compiler): +def code_finished_will_parse( + s: str, compiler: CommandCompiler +) -> Tuple[bool, bool]: """Returns a tuple of whether the buffer could be complete and whether it will parse From 711845fa99638eae4a56d699ac70ae5e5c2a7665 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 May 2022 19:05:40 +0200 Subject: [PATCH 1454/1650] Iterate directly over result of get_tokens --- bpython/curtsiesfrontend/interpreter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 80b7d5f5b..bf3f6ad80 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -96,11 +96,10 @@ def format(self, tbtext: str, lexer: Any) -> None: # FIXME: lexer should is a Lexer traceback_informative_formatter = BPythonFormatter(default_colors) traceback_code_formatter = BPythonFormatter({Token: ("d")}) - tokens = list(lexer.get_tokens(tbtext)) no_format_mode = False cur_line = [] - for token, text in tokens: + for token, text in lexer.get_tokens(tbtext): if text.endswith("\n"): cur_line.append((token, text)) if no_format_mode: From 044bc579106181f79e0113bd2b0e7bac1c37a537 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 May 2022 19:33:55 +0200 Subject: [PATCH 1455/1650] Remove unused type stub --- stubs/blessings.pyi | 47 --------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 stubs/blessings.pyi diff --git a/stubs/blessings.pyi b/stubs/blessings.pyi deleted file mode 100644 index 66fd96216..000000000 --- a/stubs/blessings.pyi +++ /dev/null @@ -1,47 +0,0 @@ -from typing import ContextManager, Text, IO - -class Terminal: - def __init__(self, stream=None, force_styling=False): - # type: (IO, bool) -> None - pass - def location(self, x=None, y=None): - # type: (int, int) -> ContextManager - pass - @property - def hide_cursor(self): - # type: () -> Text - pass - @property - def normal_cursor(self): - # type: () -> Text - pass - @property - def height(self): - # type: () -> int - pass - @property - def width(self): - # type: () -> int - pass - def fullscreen(self): - # type: () -> ContextManager - pass - def move(self, y, x): - # type: (int, int) -> Text - pass - @property - def clear_eol(self): - # type: () -> Text - pass - @property - def clear_bol(self): - # type: () -> Text - pass - @property - def clear_eos(self): - # type: () -> Text - pass - @property - def clear_eos(self): - # type: () -> Text - pass From 4ec5d763a164c37f48ca18fcf584971592648366 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 May 2022 19:42:16 +0200 Subject: [PATCH 1456/1650] Ignore a mypy bug --- bpython/curtsiesfrontend/interpreter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index bf3f6ad80..f2a83678e 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -51,7 +51,8 @@ def __init__( **options: Union[str, bool, None], ) -> None: self.f_strings = {k: f"\x01{v}" for k, v in color_scheme.items()} - super().__init__(**options) + # FIXME: mypy currently fails to handle this properly + super().__init__(**options) # type: ignore def format(self, tokensource, outfile): o = "" From df32e68b48b23d32073ecbfad36ff115d5fbade9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 May 2022 19:45:09 +0200 Subject: [PATCH 1457/1650] Some simplifications --- bpython/curtsiesfrontend/interpreter.py | 13 +++++-------- bpython/curtsiesfrontend/repl.py | 6 ++---- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index f2a83678e..82e28091c 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -94,9 +94,9 @@ def writetb(self, lines: Iterable[str]) -> None: # TODO for tracebacks get_lexer_by_name("pytb", stripall=True) def format(self, tbtext: str, lexer: Any) -> None: - # FIXME: lexer should is a Lexer + # FIXME: lexer should be "Lexer" traceback_informative_formatter = BPythonFormatter(default_colors) - traceback_code_formatter = BPythonFormatter({Token: ("d")}) + traceback_code_formatter = BPythonFormatter({Token: "d"}) no_format_mode = False cur_line = [] @@ -111,7 +111,7 @@ def format(self, tbtext: str, lexer: Any) -> None: cur_line, self.outfile ) cur_line = [] - elif text == " " and cur_line == []: + elif text == " " and len(cur_line) == 0: no_format_mode = True cur_line.append((token, text)) else: @@ -130,9 +130,6 @@ def code_finished_will_parse( False, True means code block is unfinished False, False isn't possible - an predicted error makes code block done""" try: - finished = bool(compiler(s)) - code_will_parse = True + return bool(compiler(s)), True except (ValueError, SyntaxError, OverflowError): - finished = True - code_will_parse = False - return finished, code_will_parse + return True, False diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index d5a3eed15..a29b350a4 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1363,9 +1363,8 @@ def unhighlight_paren(self): def clear_current_block(self, remove_from_history=True): self.display_buffer = [] if remove_from_history: - for unused in self.buffer: - self.history.pop() - self.all_logical_lines.pop() + del self.history[-len(self.buffer) :] + del self.all_logical_lines[-len(self.buffer) :] self.buffer = [] self.cursor_offset = 0 self.saved_indent = 0 @@ -2080,7 +2079,6 @@ def focus_on_subprocess(self, args): try: signal.signal(signal.SIGWINCH, self.orig_sigwinch_handler) with Termmode(self.orig_stdin, self.orig_tcattrs): - assert self.window is not None terminal = self.window.t with terminal.fullscreen(): sys.__stdout__.write(terminal.save) From ebc920677874007ca7e6e548665cb6b312dddb30 Mon Sep 17 00:00:00 2001 From: Ulises Date: Fri, 27 May 2022 09:00:02 +0200 Subject: [PATCH 1458/1650] Allowing sys.stind.readline receive size parameter --- bpython/curtsiesfrontend/repl.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index a29b350a4..bf723742c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -179,12 +179,17 @@ def add_input_character(self, e: str) -> None: ) self.cursor_offset += 1 - def readline(self): - self.has_focus = True - self.repl.send_to_stdin(self.current_line) - value = self.coderunner.request_from_main_context() - self.readline_results.append(value) - return value + def readline(self, size=-1): + if not isinstance(size, int): + raise TypeError(f"'{type(size).__name__}' object cannot be interpreted as an integer") + elif size == 0: + return '' + else: + self.has_focus = True + self.repl.send_to_stdin(self.current_line) + value = self.coderunner.request_from_main_context() + self.readline_results.append(value) + return value if size <= -1 else value[:size] def readlines(self, size=-1): return list(iter(self.readline, "")) From c0412f66be16cd0914d791aa4f2dfcdf85f499d4 Mon Sep 17 00:00:00 2001 From: Ulises Date: Fri, 27 May 2022 09:07:36 +0200 Subject: [PATCH 1459/1650] Applying black --- bpython/curtsiesfrontend/repl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index bf723742c..5b052435a 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -181,9 +181,11 @@ def add_input_character(self, e: str) -> None: def readline(self, size=-1): if not isinstance(size, int): - raise TypeError(f"'{type(size).__name__}' object cannot be interpreted as an integer") + raise TypeError( + f"'{type(size).__name__}' object cannot be interpreted as an integer" + ) elif size == 0: - return '' + return "" else: self.has_focus = True self.repl.send_to_stdin(self.current_line) From 624a1e1abe9df081761f79bbaf8d9faa8843f320 Mon Sep 17 00:00:00 2001 From: Ulises Date: Sat, 28 May 2022 09:33:39 +0200 Subject: [PATCH 1460/1650] adding annotations and removing else statement --- bpython/curtsiesfrontend/repl.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 5b052435a..16d8a8c83 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -179,19 +179,18 @@ def add_input_character(self, e: str) -> None: ) self.cursor_offset += 1 - def readline(self, size=-1): + def readline(self, size: int = -1) -> str: if not isinstance(size, int): raise TypeError( f"'{type(size).__name__}' object cannot be interpreted as an integer" ) elif size == 0: return "" - else: - self.has_focus = True - self.repl.send_to_stdin(self.current_line) - value = self.coderunner.request_from_main_context() - self.readline_results.append(value) - return value if size <= -1 else value[:size] + self.has_focus = True + self.repl.send_to_stdin(self.current_line) + value = self.coderunner.request_from_main_context() + self.readline_results.append(value) + return value if size <= -1 else value[:size] def readlines(self, size=-1): return list(iter(self.readline, "")) From c10ebb71ba54007f2665f7e86a966ed4b859729f Mon Sep 17 00:00:00 2001 From: Ulises Date: Sat, 28 May 2022 09:43:41 +0200 Subject: [PATCH 1461/1650] adjusting annotations --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 16d8a8c83..20188f506 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -179,7 +179,7 @@ def add_input_character(self, e: str) -> None: ) self.cursor_offset += 1 - def readline(self, size: int = -1) -> str: + def readline(self, size: int = -1) -> Union[str, Any]: if not isinstance(size, int): raise TypeError( f"'{type(size).__name__}' object cannot be interpreted as an integer" From b1966098508393db0a90a6e5c32e0598c0dbfb6b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 May 2022 17:08:01 +0200 Subject: [PATCH 1462/1650] Assert that returned value is a str Anything else would be a bug. --- bpython/curtsiesfrontend/repl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 20188f506..dd7e9b68b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -179,7 +179,7 @@ def add_input_character(self, e: str) -> None: ) self.cursor_offset += 1 - def readline(self, size: int = -1) -> Union[str, Any]: + def readline(self, size: int = -1) -> str: if not isinstance(size, int): raise TypeError( f"'{type(size).__name__}' object cannot be interpreted as an integer" @@ -189,6 +189,7 @@ def readline(self, size: int = -1) -> Union[str, Any]: self.has_focus = True self.repl.send_to_stdin(self.current_line) value = self.coderunner.request_from_main_context() + assert isinstance(value, str) self.readline_results.append(value) return value if size <= -1 else value[:size] From 6bdeadde6b6705fb9f994d6f72fd591e6c7c46c4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 May 2022 17:21:14 +0200 Subject: [PATCH 1463/1650] Also handle size argument in readlines --- bpython/curtsiesfrontend/repl.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index dd7e9b68b..c92bd57c9 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -193,8 +193,22 @@ def readline(self, size: int = -1) -> str: self.readline_results.append(value) return value if size <= -1 else value[:size] - def readlines(self, size=-1): - return list(iter(self.readline, "")) + def readlines(self, size: Optional[int] = -1) -> List[str]: + if size is None: + # the default readlines implementation also accepts None + size = -1 + if not isinstance(size, int): + raise TypeError("argument should be integer or None, not 'str'") + if size <= 0: + # read as much as we can + return list(iter(self.readline, "")) + + lines = [] + while size > 0: + line = self.readline() + lines.append(line) + size -= len(line) + return lines def __iter__(self): return iter(self.readlines()) From b7e5d8d41dd86b9240eb835b451e6dd5747f9f4d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 May 2022 17:30:40 +0200 Subject: [PATCH 1464/1650] Call clear instead of recreating the instance --- bpython/curtsiesfrontend/filewatch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 8933e2930..e70325ab5 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -32,8 +32,8 @@ def __init__( super().__init__() def reset(self) -> None: - self.dirs = defaultdict(set) - del self.modules_to_add_later[:] + self.dirs.clear() + self.modules_to_add_later.clear() self.observer.unschedule_all() def _add_module(self, path: str) -> None: @@ -70,7 +70,7 @@ def activate(self) -> None: self.observer.schedule(self, dirname, recursive=False) for module in self.modules_to_add_later: self._add_module(module) - del self.modules_to_add_later[:] + self.modules_to_add_later.clear() self.activated = True def deactivate(self) -> None: From 0c1c24f210dc04efe4e4635acc76097dbeba94b4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 May 2022 17:31:24 +0200 Subject: [PATCH 1465/1650] Simplify some of the branches --- bpython/curtsiesfrontend/repl.py | 43 +++++++++++++------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c92bd57c9..3ef3f902d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -133,35 +133,28 @@ def process_event(self, e: Union[events.Event, str]) -> None: self.current_line = "" self.cursor_offset = 0 self.repl.run_code_and_maybe_finish() - else: - if e in self.rl_char_sequences: - self.cursor_offset, self.current_line = self.rl_char_sequences[ - e - ](self.cursor_offset, self.current_line) - elif e in ("",): - pass - elif e in ("",): - if self.current_line == "": - self.repl.send_to_stdin("\n") - self.has_focus = False - self.current_line = "" - self.cursor_offset = 0 - self.repl.run_code_and_maybe_finish(for_code="") - else: - pass - elif e in ("\n", "\r", "", ""): - line = self.current_line - self.repl.send_to_stdin(line + "\n") + elif e in self.rl_char_sequences: + self.cursor_offset, self.current_line = self.rl_char_sequences[e]( + self.cursor_offset, self.current_line + ) + elif e == "": + if not len(self.current_line): + self.repl.send_to_stdin("\n") self.has_focus = False self.current_line = "" self.cursor_offset = 0 - self.repl.run_code_and_maybe_finish(for_code=line + "\n") - else: # add normal character - self.add_input_character(e) + self.repl.run_code_and_maybe_finish(for_code="") + elif e in ("\n", "\r", "", ""): + line = f"{self.current_line}\n" + self.repl.send_to_stdin(line) + self.has_focus = False + self.current_line = "" + self.cursor_offset = 0 + self.repl.run_code_and_maybe_finish(for_code=line) + elif e != "": # add normal character + self.add_input_character(e) - if self.current_line.endswith(("\n", "\r")): - pass - else: + if not self.current_line.endswith(("\n", "\r")): self.repl.send_to_stdin(self.current_line) def add_input_character(self, e: str) -> None: From f076f326fe5f50454de0c0554bdce4f58d63fb61 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 May 2022 17:32:53 +0200 Subject: [PATCH 1466/1650] Add more type annotations --- bpython/curtsiesfrontend/repl.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 3ef3f902d..c513d8661 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -13,15 +13,15 @@ from enum import Enum from types import FrameType, TracebackType from typing import ( - Dict, Any, Iterable, + Dict, List, Optional, Sequence, Tuple, - Union, Type, + Union, ) from .._typing_compat import Literal @@ -55,7 +55,11 @@ Interp, code_finished_will_parse, ) -from .manual_readline import edit_keys, cursor_on_closing_char_pair +from .manual_readline import ( + edit_keys, + cursor_on_closing_char_pair, + AbstractEdits, +) from .parse import parse as bpythonparse, func_for_letter, color_for_letter from .preprocess import preprocess from .. import __version__ @@ -105,15 +109,20 @@ class FakeStdin: In user code, sys.stdin.read() asks the user for interactive input, so this class returns control to the UI to get that input.""" - def __init__(self, coderunner, repl, configured_edit_keys=None): + def __init__( + self, + coderunner: CodeRunner, + repl: "BaseRepl", + configured_edit_keys: Optional[AbstractEdits] = None, + ): self.coderunner = coderunner self.repl = repl self.has_focus = False # whether FakeStdin receives keypress events self.current_line = "" self.cursor_offset = 0 self.old_num_lines = 0 - self.readline_results = [] - if configured_edit_keys: + self.readline_results: List[str] = [] + if configured_edit_keys is not None: self.rl_char_sequences = configured_edit_keys else: self.rl_char_sequences = edit_keys @@ -236,7 +245,7 @@ class ReevaluateFakeStdin: """Stdin mock used during reevaluation (undo) so raw_inputs don't have to be reentered""" - def __init__(self, fakestdin, repl): + def __init__(self, fakestdin: FakeStdin, repl: "BaseRepl"): self.fakestdin = fakestdin self.repl = repl self.readline_results = fakestdin.readline_results[:] From 03f4ddb2299f052953037fd0f00d05ddefdc508c Mon Sep 17 00:00:00 2001 From: jgart <47760695+jgarte@users.noreply.github.com> Date: Tue, 21 Jun 2022 23:13:50 -0500 Subject: [PATCH 1467/1650] Add GNU Guix installation instructions --- README.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.rst b/README.rst index 6cfd5663e..5e50e65a9 100644 --- a/README.rst +++ b/README.rst @@ -146,6 +146,14 @@ Fedora users can install ``bpython`` directly from the command line using ``dnf` .. code-block:: bash $ dnf install bpython + +GNU Guix +---------- +Guix users can install ``bpython`` on any GNU/Linux distribution directly from the command line: + +.. code-block:: bash + + $ guix install bpython macOS ----- From 27665ec8679ea9f57a0100672d8ab21d354179be Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Sun, 17 Jul 2022 08:41:46 +1000 Subject: [PATCH 1468/1650] docs: fix simple typo, blockied -> blocked There is a small typo in bpython/curtsiesfrontend/repl.py. Should read `blocked` rather than `blockied`. Signed-off-by: Tim Gates --- bpython/curtsiesfrontend/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index c513d8661..ef7081617 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -582,7 +582,7 @@ def request_reload(self, files_modified: Sequence[str] = ()) -> None: def schedule_refresh(self, when: float = 0) -> None: """Schedule a ScheduledRefreshRequestEvent for when. - Such a event should interrupt if blockied waiting for keyboard input""" + Such a event should interrupt if blocked waiting for keyboard input""" if self.reevaluating or self.paste_mode: self.fake_refresh_requested = True else: From 23c1945e465b5905572ebf733f64086505e5c027 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 25 Aug 2022 17:39:30 +0200 Subject: [PATCH 1469/1650] Add failing test for #966 --- bpython/test/test_inspection.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index fdbb959c9..8c1556232 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -140,6 +140,37 @@ def test_getfuncprops_numpy_array(self): # np.array(object, dtype=None, *, ...). self.assertEqual(props.argspec.args, ["object", "dtype"]) + @unittest.expectedFailure + def test_issue_966_freestanding(self): + def fun(number, lst=[]): + """ + Return a list of numbers + + Example: + ======== + C.cmethod(1337, [1, 2]) # => [1, 2, 1337] + """ + return lst + [number] + + def fun_annotations(number: int, lst: list[int] = []) -> list[int]: + """ + Return a list of numbers + + Example: + ======== + C.cmethod(1337, [1, 2]) # => [1, 2, 1337] + """ + return lst + [number] + + props = inspection.getfuncprops("fun", fun) + self.assertEqual(props.func, "fun") + self.assertEqual(props.argspec.args, ["number", "lst"]) + self.assertEqual(props.argspec.defaults[0], []) + + props = inspection.getfuncprops("fun_annotations", fun_annotations) + self.assertEqual(props.func, "fun_annotations") + self.assertEqual(props.argspec.args, ["number", "lst"]) + self.assertEqual(props.argspec.defaults[0], []) class A: a = "a" From 231adb15dc718fe10912286d360a15d14a59683c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 25 Aug 2022 17:41:29 +0200 Subject: [PATCH 1470/1650] Following handling of empty values according to the documentation --- bpython/inspection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 5ebfdbe85..67c7d0377 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -292,12 +292,12 @@ def get_argspec_from_signature(f): signature = inspect.signature(f) for parameter in signature.parameters.values(): - if parameter.annotation is not inspect._empty: + if parameter.annotation is not parameter.empty: annotations[parameter.name] = parameter.annotation if parameter.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD: args.append(parameter.name) - if parameter.default is not inspect._empty: + if parameter.default is not parameter.empty: defaults.append(parameter.default) elif parameter.kind == inspect.Parameter.POSITIONAL_ONLY: args.append(parameter.name) From 9bbb25d1125a69fe650ff36cd2201245055a60ff Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Aug 2022 23:51:59 +0200 Subject: [PATCH 1471/1650] Add another tests for function signatures --- bpython/test/test_inspection.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 8c1556232..9110fca30 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -77,6 +77,13 @@ def spam(eggs=23, foobar="yay"): self.assertEqual(repr(defaults[0]), "23") self.assertEqual(repr(defaults[1]), "'yay'") + def test_pasekeywordpairs_annotation(self): + def spam(eggs: str = "foo, bar"): + pass + + defaults = inspection.getfuncprops("spam", spam).argspec.defaults + self.assertEqual(repr(defaults[0]), "'foo, bar'") + def test_get_encoding_ascii(self): self.assertEqual(inspection.get_encoding(encoding_ascii), "ascii") self.assertEqual(inspection.get_encoding(encoding_ascii.foo), "ascii") From c6e513c91a07a2b8ed91a73c96bff8591eaee080 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Aug 2022 23:52:46 +0200 Subject: [PATCH 1472/1650] Handle type annotations in function signatures --- bpython/inspection.py | 22 ++++++++++++++++------ bpython/test/test_inspection.py | 1 - 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 67c7d0377..8f6773fbc 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -26,7 +26,7 @@ import pydoc import re from collections import namedtuple -from typing import Any, Optional, Type +from typing import Any, Optional, Type, Dict, List from types import MemberDescriptorType, TracebackType from ._typing_compat import Literal @@ -121,15 +121,16 @@ def __repr__(self): __str__ = __repr__ -def parsekeywordpairs(signature): - tokens = Python3Lexer().get_tokens(signature) +def parsekeywordpairs(signature: str) -> Dict[str, str]: preamble = True stack = [] - substack = [] + substack: List[str] = [] parendepth = 0 - for token, value in tokens: + annotation = False + for token, value in Python3Lexer().get_tokens(signature): if preamble: if token is Token.Punctuation and value == "(": + # First "(" starts the list of arguments preamble = False continue @@ -141,14 +142,23 @@ def parsekeywordpairs(signature): elif value == ":" and parendepth == -1: # End of signature reached break + elif value == ":" and parendepth == 0: + # Start of type annotation + annotation = True + if (value == "," and parendepth == 0) or ( value == ")" and parendepth == -1 ): stack.append(substack) substack = [] + # If type annotation didn't end before, ti does now. + annotation = False continue + elif token is Token.Operator and value == "=" and parendepth == 0: + # End of type annotation + annotation = False - if value and (parendepth > 0 or value.strip()): + if value and not annotation and (parendepth > 0 or value.strip()): substack.append(value) return {item[0]: "".join(item[2:]) for item in stack if len(item) >= 3} diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 9110fca30..333abbbd4 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -147,7 +147,6 @@ def test_getfuncprops_numpy_array(self): # np.array(object, dtype=None, *, ...). self.assertEqual(props.argspec.args, ["object", "dtype"]) - @unittest.expectedFailure def test_issue_966_freestanding(self): def fun(number, lst=[]): """ From c2e3faca367162dddf1e3f7cd062a5cb770db7cf Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 27 Aug 2022 09:33:49 +0200 Subject: [PATCH 1473/1650] Fix type annotation --- bpython/test/test_inspection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 333abbbd4..ba9ffca7a 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -2,6 +2,7 @@ import os import sys import unittest +from typing import List from bpython import inspection from bpython.test.fodder import encoding_ascii @@ -158,7 +159,7 @@ def fun(number, lst=[]): """ return lst + [number] - def fun_annotations(number: int, lst: list[int] = []) -> list[int]: + def fun_annotations(number: int, lst: List[int] = []) -> List[int]: """ Return a list of numbers From d42b21aaef45b9fec0519fbf044354ec81986c74 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Aug 2022 18:09:23 +0200 Subject: [PATCH 1474/1650] Turn FuncProps into a dataclass --- bpython/autocomplete.py | 6 +++--- bpython/cli.py | 6 +++--- bpython/inspection.py | 15 +++++++++++---- bpython/test/test_autocomplete.py | 6 +++--- bpython/urwid.py | 4 +++- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 81cea8945..e623359ef 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -570,11 +570,11 @@ def matches( matches = { f"{name}=" - for name in argspec[1][0] + for name in argspec.argspec[0] if isinstance(name, str) and name.startswith(r.word) } matches.update( - name + "=" for name in argspec[1][4] if name.startswith(r.word) + name + "=" for name in argspec.argspec[4] if name.startswith(r.word) ) return matches if matches else None @@ -711,7 +711,7 @@ def get_completer( line is a string of the current line kwargs (all optional): locals_ is a dictionary of the environment - argspec is an inspect.ArgSpec instance for the current function where + argspec is an inspect.FuncProps instance for the current function where the cursor is current_block is the possibly multiline not-yet-evaluated block of code which the current line is part of diff --git a/bpython/cli.py b/bpython/cli.py index 2512d6ffe..ba85729f0 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -101,7 +101,7 @@ from . import translations from .translations import _ -from . import repl +from . import repl, inspection from . import args as bpargs from .pager import page from .args import parse as argsparse @@ -726,7 +726,7 @@ def lf(self) -> None: def mkargspec( self, - topline: Any, # Named tuples don't seem to play nice with mypy + topline: inspection.FuncProps, in_arg: Union[str, int, None], down: bool, ) -> int: @@ -1298,7 +1298,7 @@ def show_list( self, items: List[str], arg_pos: Union[str, int, None], - topline: Any = None, # Named tuples don't play nice with mypy + topline: Optional[inspection.FuncProps] = None, formatter: Optional[Callable] = None, current_item: Union[str, Literal[False]] = None, ) -> None: diff --git a/bpython/inspection.py b/bpython/inspection.py index 8f6773fbc..4bd67b4fd 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -26,7 +26,8 @@ import pydoc import re from collections import namedtuple -from typing import Any, Optional, Type, Dict, List +from dataclasses import dataclass +from typing import Any, Callable, Optional, Type, Dict, List from types import MemberDescriptorType, TracebackType from ._typing_compat import Literal @@ -35,6 +36,7 @@ from .lazyre import LazyReCompile + ArgSpec = namedtuple( "ArgSpec", [ @@ -48,7 +50,12 @@ ], ) -FuncProps = namedtuple("FuncProps", ["func", "argspec", "is_bound_method"]) + +@dataclass +class FuncProps: + func: str + argspec: ArgSpec + is_bound_method: bool class AttrCleaner: @@ -112,10 +119,10 @@ class _Repr: Helper for `fixlongargs()`: Returns the given value in `__repr__()`. """ - def __init__(self, value): + def __init__(self, value: str) -> None: self.value = value - def __repr__(self): + def __repr__(self) -> str: return self.value __str__ = __repr__ diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 8b171d035..bfc3b8308 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -11,7 +11,7 @@ except ImportError: has_jedi = False -from bpython import autocomplete +from bpython import autocomplete, inspection from bpython.line import LinePart glob_function = "glob.iglob" @@ -418,8 +418,8 @@ def test_set_of_params_returns_when_matches_found(self): def func(apple, apricot, banana, carrot): pass - argspec = list(inspect.getfullargspec(func)) - argspec = ["func", argspec, False] + argspec = inspection.ArgSpec(*inspect.getfullargspec(func)) + argspec = inspection.FuncProps("func", argspec, False) com = autocomplete.ParameterNameCompletion() self.assertSetEqual( com.matches(1, "a", argspec=argspec), {"apple=", "apricot="} diff --git a/bpython/urwid.py b/bpython/urwid.py index 0a2ffe31b..6b329e651 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -762,7 +762,9 @@ def _populate_completion(self): if self.complete(): if self.funcprops: # This is mostly just stolen from the cli module. - func_name, args, is_bound = self.funcprops + func_name = self.funcprops.func + args = self.funcprops.argspec + is_bound = self.funcprops.is_bound_method in_arg = self.arg_pos args, varargs, varkw, defaults = args[:4] kwonly = self.funcprops.argspec.kwonly From ba1dac78d1623a4b9a2498a7f6b03ca7c6b4e01f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Aug 2022 18:09:48 +0200 Subject: [PATCH 1475/1650] Remove an if block that is never executed --- bpython/inspection.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 4bd67b4fd..895756b52 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -270,8 +270,6 @@ def getfuncprops(func, f): try: argspec = get_argspec_from_signature(f) fixlongargs(f, argspec) - if len(argspec) == 4: - argspec = argspec + [list(), dict(), None] argspec = ArgSpec(*argspec) fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError, ValueError): From 84a54677b92c6d9c124653e9c28a57ff3339312a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Aug 2022 18:25:49 +0200 Subject: [PATCH 1476/1650] Turn ArgSpec into a dataclass --- bpython/autocomplete.py | 4 +-- bpython/inspection.py | 69 ++++++++++++++++------------------------- bpython/urwid.py | 6 ++-- 3 files changed, 33 insertions(+), 46 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index e623359ef..9acc95ebf 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -570,11 +570,11 @@ def matches( matches = { f"{name}=" - for name in argspec.argspec[0] + for name in argspec.argspec.args if isinstance(name, str) and name.startswith(r.word) } matches.update( - name + "=" for name in argspec.argspec[4] if name.startswith(r.word) + name + "=" for name in argspec.argspec.kwonly if name.startswith(r.word) ) return matches if matches else None diff --git a/bpython/inspection.py b/bpython/inspection.py index 895756b52..676c3aa34 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -25,7 +25,6 @@ import keyword import pydoc import re -from collections import namedtuple from dataclasses import dataclass from typing import Any, Callable, Optional, Type, Dict, List from types import MemberDescriptorType, TracebackType @@ -37,18 +36,15 @@ from .lazyre import LazyReCompile -ArgSpec = namedtuple( - "ArgSpec", - [ - "args", - "varargs", - "varkwargs", - "defaults", - "kwonly", - "kwonly_defaults", - "annotations", - ], -) +@dataclass +class ArgSpec: + args: List[str] + varargs: Optional[str] + varkwargs: Optional[str] + defaults: Optional[List[Any]] + kwonly: List[str] + kwonly_defaults: Optional[Dict[str, Any]] + annotations: Optional[Dict[str, Any]] @dataclass @@ -171,24 +167,24 @@ def parsekeywordpairs(signature: str) -> Dict[str, str]: return {item[0]: "".join(item[2:]) for item in stack if len(item) >= 3} -def fixlongargs(f, argspec): +def fixlongargs(f: Callable, argspec: ArgSpec) -> ArgSpec: """Functions taking default arguments that are references to other objects whose str() is too big will cause breakage, so we swap out the object itself with the name it was referenced with in the source by parsing the source itself !""" - if argspec[3] is None: + if argspec.defaults is None: # No keyword args, no need to do anything - return - values = list(argspec[3]) + return argspec + values = list(argspec.defaults) if not values: - return - keys = argspec[0][-len(values) :] + return argspec + keys = argspec.args[-len(values) :] try: src = inspect.getsourcelines(f) except (OSError, IndexError): # IndexError is raised in inspect.findsource(), can happen in # some situations. See issue #94. - return + return argspec signature = "".join(src[0]) kwparsed = parsekeywordpairs(signature) @@ -196,7 +192,8 @@ def fixlongargs(f, argspec): if len(repr(value)) != len(kwparsed[key]): values[i] = _Repr(kwparsed[key]) - argspec[3] = values + argspec.defaults = values + return argspec getpydocspec_re = LazyReCompile( @@ -247,7 +244,7 @@ def getpydocspec(f, func): ) -def getfuncprops(func, f): +def getfuncprops(func: str, f: Callable) -> Optional[FuncProps]: # Check if it's a real bound method or if it's implicitly calling __init__ # (i.e. FooClass(...) and not FooClass.__init__(...) -- the former would # not take 'self', the latter would: @@ -268,9 +265,8 @@ def getfuncprops(func, f): # '__init__' throws xmlrpclib.Fault (see #202) return None try: - argspec = get_argspec_from_signature(f) - fixlongargs(f, argspec) - argspec = ArgSpec(*argspec) + argspec = _get_argspec_from_signature(f) + argspec = fixlongargs(f, argspec) fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError, ValueError): argspec = getpydocspec(f, func) @@ -289,7 +285,7 @@ def is_eval_safe_name(string: str) -> bool: ) -def get_argspec_from_signature(f): +def _get_argspec_from_signature(f: Callable) -> ArgSpec: """Get callable signature from inspect.signature in argspec format. inspect.signature is a Python 3 only function that returns the signature of @@ -324,26 +320,15 @@ def get_argspec_from_signature(f): elif parameter.kind == inspect.Parameter.VAR_KEYWORD: varkwargs = parameter.name - # inspect.getfullargspec returns None for 'defaults', 'kwonly_defaults' and - # 'annotations' if there are no values for them. - if not defaults: - defaults = None - - if not kwonly_defaults: - kwonly_defaults = None - - if not annotations: - annotations = None - - return [ + return ArgSpec( args, varargs, varkwargs, - defaults, + defaults if defaults else None, kwonly, - kwonly_defaults, - annotations, - ] + kwonly_defaults if kwonly_defaults else None, + annotations if annotations else None, + ) get_encoding_line_re = LazyReCompile(r"^.*coding[:=]\s*([-\w.]+).*$") diff --git a/bpython/urwid.py b/bpython/urwid.py index 6b329e651..cc828ff06 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -763,10 +763,12 @@ def _populate_completion(self): if self.funcprops: # This is mostly just stolen from the cli module. func_name = self.funcprops.func - args = self.funcprops.argspec + args = self.funcprops.argspec.args is_bound = self.funcprops.is_bound_method in_arg = self.arg_pos - args, varargs, varkw, defaults = args[:4] + varargs = self.funcprops.argspec.varargs + varkw = self.funcprops.argspec.varkwargs + defaults = self.funcprops.argspec.defaults kwonly = self.funcprops.argspec.kwonly kwonly_defaults = self.funcprops.argspec.kwonly_defaults or {} markup = [("bold name", func_name), ("name", ": (")] From 9861730ed39b5d9c9dfa97b53e337670d471f4a5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Aug 2022 18:34:55 +0200 Subject: [PATCH 1477/1650] Some clean up --- bpython/inspection.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 676c3aa34..ed201d9a7 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -185,8 +185,7 @@ def fixlongargs(f: Callable, argspec: ArgSpec) -> ArgSpec: # IndexError is raised in inspect.findsource(), can happen in # some situations. See issue #94. return argspec - signature = "".join(src[0]) - kwparsed = parsekeywordpairs(signature) + kwparsed = parsekeywordpairs("".join(src[0])) for i, (key, value) in enumerate(zip(keys, values)): if len(repr(value)) != len(kwparsed[key]): @@ -201,7 +200,7 @@ def fixlongargs(f: Callable, argspec: ArgSpec) -> ArgSpec: ) -def getpydocspec(f, func): +def _getpydocspec(f: Callable) -> Optional[ArgSpec]: try: argspec = pydoc.getdoc(f) except NameError: @@ -218,7 +217,7 @@ def getpydocspec(f, func): defaults = [] varargs = varkwargs = None kwonly_args = [] - kwonly_defaults = dict() + kwonly_defaults = {} for arg in s.group(2).split(","): arg = arg.strip() if arg.startswith("**"): @@ -266,15 +265,14 @@ def getfuncprops(func: str, f: Callable) -> Optional[FuncProps]: return None try: argspec = _get_argspec_from_signature(f) - argspec = fixlongargs(f, argspec) - fprops = FuncProps(func, argspec, is_bound_method) + fprops = FuncProps(func, fixlongargs(f, argspec), is_bound_method) except (TypeError, KeyError, ValueError): - argspec = getpydocspec(f, func) - if argspec is None: + argspec_pydoc = _getpydocspec(f) + if argspec_pydoc is None: return None if inspect.ismethoddescriptor(f): - argspec.args.insert(0, "obj") - fprops = FuncProps(func, argspec, is_bound_method) + argspec_pydoc.args.insert(0, "obj") + fprops = FuncProps(func, argspec_pydoc, is_bound_method) return fprops From b54039db00aa622d131c681fc9acfd30dc4e1362 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Aug 2022 22:00:46 +0200 Subject: [PATCH 1478/1650] Make kwargs explicit with proper type annotations Also fix handling of complete_magic_methods. --- bpython/autocomplete.py | 119 ++++++++++++++++++++---------- bpython/test/test_autocomplete.py | 14 ++-- 2 files changed, 90 insertions(+), 43 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 9acc95ebf..f8e97d73a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -28,6 +28,7 @@ import __main__ import abc import glob +import itertools import keyword import logging import os @@ -383,11 +384,15 @@ class AttrCompletion(BaseCompletionType): attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") def matches( - self, cursor_offset: int, line: str, **kwargs: Any + self, + cursor_offset: int, + line: str, + *, + locals_: Optional[Dict[str, Any]] = None, + **kwargs: Any, ) -> Optional[Set]: - if "locals_" not in kwargs: + if locals_ is None: return None - locals_ = cast(Dict[str, Any], kwargs["locals_"]) r = self.locate(cursor_offset, line) if r is None: @@ -465,11 +470,15 @@ def list_attributes(self, obj: Any) -> List[str]: class DictKeyCompletion(BaseCompletionType): def matches( - self, cursor_offset: int, line: str, **kwargs: Any + self, + cursor_offset: int, + line: str, + *, + locals_: Optional[Dict[str, Any]] = None, + **kwargs: Any, ) -> Optional[Set]: - if "locals_" not in kwargs: + if locals_ is None: return None - locals_ = kwargs["locals_"] r = self.locate(cursor_offset, line) if r is None: @@ -500,11 +509,16 @@ def format(self, match: str) -> str: class MagicMethodCompletion(BaseCompletionType): def matches( - self, cursor_offset: int, line: str, **kwargs: Any + self, + cursor_offset: int, + line: str, + *, + current_block: Optional[str] = None, + complete_magic_methods: Optional[bool] = None, + **kwargs: Any, ) -> Optional[Set]: - if "current_block" not in kwargs: + if current_block is None or complete_magic_methods is None or not complete_magic_methods: return None - current_block = kwargs["current_block"] r = self.locate(cursor_offset, line) if r is None: @@ -519,15 +533,19 @@ def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: class GlobalCompletion(BaseCompletionType): def matches( - self, cursor_offset: int, line: str, **kwargs: Any + self, + cursor_offset: int, + line: str, + *, + locals_: Optional[Dict[str, Any]] = None, + **kwargs: Any, ) -> Optional[Set]: """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace that match. """ - if "locals_" not in kwargs: + if locals_ is None: return None - locals_ = kwargs["locals_"] r = self.locate(cursor_offset, line) if r is None: @@ -556,25 +574,29 @@ def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: class ParameterNameCompletion(BaseCompletionType): def matches( - self, cursor_offset: int, line: str, **kwargs: Any + self, + cursor_offset: int, + line: str, + *, + funcprops: Optional[inspection.FuncProps] = None, + **kwargs: Any, ) -> Optional[Set]: - if "argspec" not in kwargs: + if funcprops is None: return None - argspec = kwargs["argspec"] - if not argspec: - return None r = self.locate(cursor_offset, line) if r is None: return None matches = { f"{name}=" - for name in argspec.argspec.args + for name in funcprops.argspec.args if isinstance(name, str) and name.startswith(r.word) } matches.update( - name + "=" for name in argspec.argspec.kwonly if name.startswith(r.word) + name + "=" + for name in funcprops.argspec.kwonly + if name.startswith(r.word) ) return matches if matches else None @@ -588,12 +610,13 @@ def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: return lineparts.current_expression_attribute(cursor_offset, line) def matches( - self, cursor_offset: int, line: str, **kwargs: Any + self, + cursor_offset: int, + line: str, + *, + locals_: Optional[Dict[str, Any]] = None, + **kwargs: Any, ) -> Optional[Set]: - if "locals_" not in kwargs: - return None - locals_ = kwargs["locals_"] - if locals_ is None: locals_ = __main__.__dict__ @@ -629,20 +652,23 @@ class JediCompletion(BaseCompletionType): _orig_start: Optional[int] def matches( - self, cursor_offset: int, line: str, **kwargs: Any + self, + cursor_offset: int, + line: str, + *, + history: Optional[List[str]] = None, + **kwargs: Any, ) -> Optional[Set]: - if "history" not in kwargs: + if history is None: return None - history = kwargs["history"] - if not lineparts.current_word(cursor_offset, line): return None - history = "\n".join(history) + "\n" + line + combined_history = "\n".join(itertools.chain(history, (line,))) try: - script = jedi.Script(history, path="fake.py") + script = jedi.Script(combined_history, path="fake.py") completions = script.complete( - len(history.splitlines()), cursor_offset + len(combined_history.splitlines()), cursor_offset ) except (jedi.NotFoundError, IndexError, KeyError): # IndexError for #483 @@ -679,12 +705,16 @@ def locate(self, cursor_offset: int, line: str) -> LinePart: class MultilineJediCompletion(JediCompletion): # type: ignore [no-redef] def matches( - self, cursor_offset: int, line: str, **kwargs: Any + self, + cursor_offset: int, + line: str, + *, + current_block: Optional[str] = None, + history: Optional[List[str]] = None, + **kwargs: Any, ) -> Optional[Set]: - if "current_block" not in kwargs or "history" not in kwargs: + if current_block is None or history is None: return None - current_block = kwargs["current_block"] - history = kwargs["history"] if "\n" in current_block: assert cursor_offset <= len(line), "{!r} {!r}".format( @@ -701,7 +731,12 @@ def get_completer( completers: Sequence[BaseCompletionType], cursor_offset: int, line: str, - **kwargs: Any, + *, + locals_: Optional[Dict[str, Any]] = None, + argspec: Optional[inspection.FuncProps] = None, + history: Optional[List[str]] = None, + current_block: Optional[str] = None, + complete_magic_methods: Optional[bool] = None, ) -> Tuple[List[str], Optional[BaseCompletionType]]: """Returns a list of matches and an applicable completer @@ -711,7 +746,7 @@ def get_completer( line is a string of the current line kwargs (all optional): locals_ is a dictionary of the environment - argspec is an inspect.FuncProps instance for the current function where + argspec is an inspection.FuncProps instance for the current function where the cursor is current_block is the possibly multiline not-yet-evaluated block of code which the current line is part of @@ -721,7 +756,15 @@ def get_completer( for completer in completers: try: - matches = completer.matches(cursor_offset, line, **kwargs) + matches = completer.matches( + cursor_offset, + line, + locals_=locals_, + funcprops=argspec, + history=history, + current_block=current_block, + complete_magic_methods=complete_magic_methods, + ) except Exception as e: # Instead of crashing the UI, log exceptions from autocompleters. logger = logging.getLogger(__name__) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index bfc3b8308..88e990542 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -324,7 +324,7 @@ def test_magic_methods_complete_after_double_underscores(self): com = autocomplete.MagicMethodCompletion() block = "class Something(object)\n def __" self.assertSetEqual( - com.matches(10, " def __", current_block=block), + com.matches(10, " def __", current_block=block, complete_magic_methods=True), set(autocomplete.MAGIC_METHODS), ) @@ -419,10 +419,14 @@ def func(apple, apricot, banana, carrot): pass argspec = inspection.ArgSpec(*inspect.getfullargspec(func)) - argspec = inspection.FuncProps("func", argspec, False) + funcspec = inspection.FuncProps("func", argspec, False) com = autocomplete.ParameterNameCompletion() self.assertSetEqual( - com.matches(1, "a", argspec=argspec), {"apple=", "apricot="} + com.matches(1, "a", funcprops=funcspec), {"apple=", "apricot="} + ) + self.assertSetEqual( + com.matches(2, "ba", funcprops=funcspec), {"banana="} + ) + self.assertSetEqual( + com.matches(3, "car", funcprops=funcspec), {"carrot="} ) - self.assertSetEqual(com.matches(2, "ba", argspec=argspec), {"banana="}) - self.assertSetEqual(com.matches(3, "car", argspec=argspec), {"carrot="}) From 15338a2515c9e33ea24852b66bb56e84a2dca154 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Aug 2022 22:06:48 +0200 Subject: [PATCH 1479/1650] Apply black --- bpython/autocomplete.py | 6 +++++- bpython/test/test_autocomplete.py | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index f8e97d73a..36e175433 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -517,7 +517,11 @@ def matches( complete_magic_methods: Optional[bool] = None, **kwargs: Any, ) -> Optional[Set]: - if current_block is None or complete_magic_methods is None or not complete_magic_methods: + if ( + current_block is None + or complete_magic_methods is None + or not complete_magic_methods + ): return None r = self.locate(cursor_offset, line) diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 88e990542..c95328fbb 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -324,7 +324,12 @@ def test_magic_methods_complete_after_double_underscores(self): com = autocomplete.MagicMethodCompletion() block = "class Something(object)\n def __" self.assertSetEqual( - com.matches(10, " def __", current_block=block, complete_magic_methods=True), + com.matches( + 10, + " def __", + current_block=block, + complete_magic_methods=True, + ), set(autocomplete.MAGIC_METHODS), ) From 12e46590e71e4039faaf7ed467b14972179d25dd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 28 May 2022 17:44:21 +0200 Subject: [PATCH 1480/1650] Use super --- bpdb/debugger.py | 4 ++-- bpython/cli.py | 3 +-- bpython/urwid.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/bpdb/debugger.py b/bpdb/debugger.py index b98e9612a..3e5bbc91b 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -28,14 +28,14 @@ class BPdb(pdb.Pdb): """PDB with BPython support.""" def __init__(self, *args, **kwargs): - pdb.Pdb.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.prompt = "(BPdb) " self.intro = 'Use "B" to enter bpython, Ctrl-d to exit it.' def postloop(self): # We only want to show the intro message once. self.intro = None - pdb.Pdb.postloop(self) + super().postloop() # cmd.Cmd commands diff --git a/bpython/cli.py b/bpython/cli.py index ba85729f0..886dc2c85 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1128,8 +1128,7 @@ def push(self, s: str, insert_into_history: bool = True) -> bool: # curses.raw(True) prevents C-c from causing a SIGINT curses.raw(False) try: - x: bool = repl.Repl.push(self, s, insert_into_history) - return x + return super().push(s, insert_into_history) except SystemExit as e: # Avoid a traceback on e.g. quit() self.do_exit = True diff --git a/bpython/urwid.py b/bpython/urwid.py index cc828ff06..66054097e 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -938,7 +938,7 @@ def push(self, s, insert_into_history=True): signal.signal(signal.SIGINT, signal.default_int_handler) # Pretty blindly adapted from bpython.cli try: - return repl.Repl.push(self, s, insert_into_history) + return super().push(s, insert_into_history) except SystemExit as e: self.exit_value = e.args raise urwid.ExitMainLoop() From df03931ad5dd516ed7ab0498d5c07024af204e1c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Aug 2022 22:23:12 +0200 Subject: [PATCH 1481/1650] Fix mypy and black regressions --- bpython/autocomplete.py | 6 +----- bpython/repl.py | 4 ++-- bpython/test/test_inspection.py | 1 + 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 36e175433..3dbf80bd9 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -39,14 +39,13 @@ from enum import Enum from typing import ( Any, - cast, Dict, Iterator, List, Optional, + Sequence, Set, Tuple, - Sequence, ) from . import inspection from . import line as lineparts @@ -391,9 +390,6 @@ def matches( locals_: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Optional[Set]: - if locals_ is None: - return None - r = self.locate(cursor_offset, line) if r is None: return None diff --git a/bpython/repl.py b/bpython/repl.py index e41536fd0..6e593b2b5 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -588,7 +588,7 @@ def current_string(self, concatenate=False): def get_object(self, name): attributes = name.split(".") - obj = eval(attributes.pop(0), self.interp.locals) + obj = eval(attributes.pop(0), cast(Dict[str, Any], self.interp.locals)) while attributes: with inspection.AttrCleaner(obj): obj = getattr(obj, attributes.pop(0)) @@ -783,7 +783,7 @@ def complete(self, tab: bool = False) -> Optional[bool]: self.completers, cursor_offset=self.cursor_offset, line=self.current_line, - locals_=self.interp.locals, + locals_=cast(Dict[str, Any], self.interp.locals), argspec=self.funcprops, current_block="\n".join(self.buffer + [self.current_line]), complete_magic_methods=self.config.complete_magic_methods, diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index ba9ffca7a..b7e2d9b5c 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -179,6 +179,7 @@ def fun_annotations(number: int, lst: List[int] = []) -> List[int]: self.assertEqual(props.argspec.args, ["number", "lst"]) self.assertEqual(props.argspec.defaults[0], []) + class A: a = "a" From 4a55b9e4ffc45e4f3c33fe30ff5ce28f4d65bc9b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 28 Aug 2022 23:34:35 +0200 Subject: [PATCH 1482/1650] Bump curtsies to 0.4.0 --- README.rst | 2 +- requirements.txt | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 5e50e65a9..dd307d33b 100644 --- a/README.rst +++ b/README.rst @@ -91,7 +91,7 @@ your config file as **~/.config/bpython/config** (i.e. Dependencies ============ * Pygments -* curtsies >= 0.3.5 +* curtsies >= 0.4.0 * greenlet * pyxdg * requests diff --git a/requirements.txt b/requirements.txt index 7f56dc0fd..ba8b126d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Pygments backports.cached-property; python_version < "3.8" -curtsies >=0.3.5 +curtsies >=0.4.0 cwcwidth greenlet pyxdg diff --git a/setup.cfg b/setup.cfg index f5c1bc849..748ce965e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,7 +21,7 @@ packages = bpdb install_requires = backports.cached-property; python_version < "3.8" - curtsies >=0.3.5 + curtsies >=0.4.0 cwcwidth greenlet pygments From ddd4321d0a9b27b9a2a3efc6a23d150849f383ac Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 08:10:08 +0200 Subject: [PATCH 1483/1650] Hide implementation details --- bpython/inspection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index ed201d9a7..3d6096058 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -167,7 +167,7 @@ def parsekeywordpairs(signature: str) -> Dict[str, str]: return {item[0]: "".join(item[2:]) for item in stack if len(item) >= 3} -def fixlongargs(f: Callable, argspec: ArgSpec) -> ArgSpec: +def _fixlongargs(f: Callable, argspec: ArgSpec) -> ArgSpec: """Functions taking default arguments that are references to other objects whose str() is too big will cause breakage, so we swap out the object itself with the name it was referenced with in the source by parsing the @@ -195,7 +195,7 @@ def fixlongargs(f: Callable, argspec: ArgSpec) -> ArgSpec: return argspec -getpydocspec_re = LazyReCompile( +_getpydocspec_re = LazyReCompile( r"([a-zA-Z_][a-zA-Z0-9_]*?)\((.*?)\)", re.DOTALL ) @@ -206,7 +206,7 @@ def _getpydocspec(f: Callable) -> Optional[ArgSpec]: except NameError: return None - s = getpydocspec_re.search(argspec) + s = _getpydocspec_re.search(argspec) if s is None: return None @@ -265,7 +265,7 @@ def getfuncprops(func: str, f: Callable) -> Optional[FuncProps]: return None try: argspec = _get_argspec_from_signature(f) - fprops = FuncProps(func, fixlongargs(f, argspec), is_bound_method) + fprops = FuncProps(func, _fixlongargs(f, argspec), is_bound_method) except (TypeError, KeyError, ValueError): argspec_pydoc = _getpydocspec(f) if argspec_pydoc is None: From d48a3d2401c0f557a0d1d34539a542a206d1d480 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 08:14:18 +0200 Subject: [PATCH 1484/1650] Use getattr_safe instead of AttrCleaner This attribute lookup is covered by getattr_safe. --- bpython/repl.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 6e593b2b5..8aea4e172 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -586,12 +586,11 @@ def current_string(self, concatenate=False): return "" return "".join(string) - def get_object(self, name): + def get_object(self, name: str) -> Any: attributes = name.split(".") obj = eval(attributes.pop(0), cast(Dict[str, Any], self.interp.locals)) while attributes: - with inspection.AttrCleaner(obj): - obj = getattr(obj, attributes.pop(0)) + obj = inspection.getattr_safe(obj, attributes.pop(0)) return obj @classmethod From e59e924c57c72c732180b134ba938307cf45f756 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 09:10:30 +0200 Subject: [PATCH 1485/1650] Add another failing test for #966 --- bpython/test/test_inspection.py | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index b7e2d9b5c..0dfaab896 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -2,6 +2,7 @@ import os import sys import unittest +from collections.abc import Sequence from typing import List from bpython import inspection @@ -179,6 +180,44 @@ def fun_annotations(number: int, lst: List[int] = []) -> List[int]: self.assertEqual(props.argspec.args, ["number", "lst"]) self.assertEqual(props.argspec.defaults[0], []) + @unittest.expectedFailure + def test_issue_966_class_method(self): + class Issue966(Sequence): + @classmethod + def cmethod(cls, number: int, lst: List[int] = []): + """ + Return a list of numbers + + Example: + ======== + C.cmethod(1337, [1, 2]) # => [1, 2, 1337] + """ + return lst + [number] + + @classmethod + def bmethod(cls, number, lst): + """ + Return a list of numbers + + Example: + ======== + C.cmethod(1337, [1, 2]) # => [1, 2, 1337] + """ + return lst + [number] + + props = inspection.getfuncprops( + "bmethod", inspection.getattr_safe(Issue966, "bmethod") + ) + self.assertEqual(props.func, "bmethod") + self.assertEqual(props.argspec.args, ["number", "lst"]) + + props = inspection.getfuncprops( + "cmethod", inspection.getattr_safe(Issue966, "cmethod") + ) + self.assertEqual(props.func, "cmethod") + self.assertEqual(props.argspec.args, ["number", "lst"]) + self.assertEqual(props.argspec.defaults[0], []) + class A: a = "a" From fb923fde21b45fa96c6cc75a24034faac74837e4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 18:17:36 +0200 Subject: [PATCH 1486/1650] Hide implementation details --- bpython/inspection.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 3d6096058..a11298e30 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -59,14 +59,12 @@ class AttrCleaner: on attribute lookup.""" def __init__(self, obj: Any) -> None: - self.obj = obj + self._obj = obj def __enter__(self) -> None: """Try to make an object not exhibit side-effects on attribute lookup.""" - type_ = type(self.obj) - __getattribute__ = None - __getattr__ = None + type_ = type(self._obj) # Dark magic: # If __getattribute__ doesn't exist on the class and __getattr__ does # then __getattr__ will be called when doing @@ -89,7 +87,7 @@ def __enter__(self) -> None: except TypeError: # XXX: This happens for e.g. built-in types __getattribute__ = None - self.attribs = (__getattribute__, __getattr__) + self._attribs = (__getattribute__, __getattr__) # /Dark magic def __exit__( @@ -99,8 +97,8 @@ def __exit__( exc_tb: Optional[TracebackType], ) -> Literal[False]: """Restore an object's magic methods.""" - type_ = type(self.obj) - __getattribute__, __getattr__ = self.attribs + type_ = type(self._obj) + __getattribute__, __getattr__ = self._attribs # Dark magic: if __getattribute__ is not None: setattr(type_, "__getattribute__", __getattribute__) @@ -329,13 +327,13 @@ def _get_argspec_from_signature(f: Callable) -> ArgSpec: ) -get_encoding_line_re = LazyReCompile(r"^.*coding[:=]\s*([-\w.]+).*$") +_get_encoding_line_re = LazyReCompile(r"^.*coding[:=]\s*([-\w.]+).*$") def get_encoding(obj) -> str: """Try to obtain encoding information of the source of an object.""" for line in inspect.findsource(obj)[0][:2]: - m = get_encoding_line_re.search(line) + m = _get_encoding_line_re.search(line) if m: return m.group(1) return "utf8" @@ -344,9 +342,9 @@ def get_encoding(obj) -> str: def get_encoding_file(fname: str) -> str: """Try to obtain encoding information from a Python source file.""" with open(fname, encoding="ascii", errors="ignore") as f: - for unused in range(2): + for _ in range(2): line = f.readline() - match = get_encoding_line_re.search(line) + match = _get_encoding_line_re.search(line) if match: return match.group(1) return "utf8" From 3ad203dc1ec9e27e2e2408dd8c297bcb3c35f52a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 18:17:47 +0200 Subject: [PATCH 1487/1650] Implement ContextManager for AttrCleaner Also extend some documentation. --- bpython/autocomplete.py | 5 +++-- bpython/inspection.py | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 3dbf80bd9..782b8b87a 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -458,8 +458,9 @@ def attr_lookup(self, obj: Any, expr: str, attr: str) -> List: return matches def list_attributes(self, obj: Any) -> List[str]: - # TODO: re-implement dir using getattr_static to avoid using - # AttrCleaner here? + # TODO: re-implement dir without AttrCleaner here + # + # Note: accessing `obj.__dir__` via `getattr_static` is not side-effect free. with inspection.AttrCleaner(obj): return dir(obj) diff --git a/bpython/inspection.py b/bpython/inspection.py index a11298e30..4268f4fbf 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -26,7 +26,7 @@ import pydoc import re from dataclasses import dataclass -from typing import Any, Callable, Optional, Type, Dict, List +from typing import Any, Callable, Optional, Type, Dict, List, ContextManager from types import MemberDescriptorType, TracebackType from ._typing_compat import Literal @@ -54,9 +54,11 @@ class FuncProps: is_bound_method: bool -class AttrCleaner: +class AttrCleaner(ContextManager[None]): """A context manager that tries to make an object not exhibit side-effects - on attribute lookup.""" + on attribute lookup. + + Unless explicitely required, prefer `getattr_safe`.""" def __init__(self, obj: Any) -> None: self._obj = obj @@ -351,7 +353,7 @@ def get_encoding_file(fname: str) -> str: def getattr_safe(obj: Any, name: str) -> Any: - """side effect free getattr (calls getattr_static).""" + """Side effect free getattr (calls getattr_static).""" result = inspect.getattr_static(obj, name) # Slots are a MemberDescriptorType if isinstance(result, MemberDescriptorType): From 1332d18b53fbe99f2808febd035dbbb4647c02c9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 18:20:44 +0200 Subject: [PATCH 1488/1650] Test inspection with static methods This is another variant of the class from #966. --- bpython/test/test_inspection.py | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 0dfaab896..3cb87d396 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -218,6 +218,43 @@ def bmethod(cls, number, lst): self.assertEqual(props.argspec.args, ["number", "lst"]) self.assertEqual(props.argspec.defaults[0], []) + def test_issue_966_static_method(self): + class Issue966(Sequence): + @staticmethod + def cmethod(number: int, lst: List[int] = []): + """ + Return a list of numbers + + Example: + ======== + C.cmethod(1337, [1, 2]) # => [1, 2, 1337] + """ + return lst + [number] + + @staticmethod + def bmethod(number, lst): + """ + Return a list of numbers + + Example: + ======== + C.cmethod(1337, [1, 2]) # => [1, 2, 1337] + """ + return lst + [number] + + props = inspection.getfuncprops( + "bmethod", inspection.getattr_safe(Issue966, "bmethod") + ) + self.assertEqual(props.func, "bmethod") + self.assertEqual(props.argspec.args, ["number", "lst"]) + + props = inspection.getfuncprops( + "cmethod", inspection.getattr_safe(Issue966, "cmethod") + ) + self.assertEqual(props.func, "cmethod") + self.assertEqual(props.argspec.args, ["number", "lst"]) + self.assertEqual(props.argspec.defaults[0], []) + class A: a = "a" From 1bc255b87dddf78cfef204b68497ea71ba0b10f8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 18:24:02 +0200 Subject: [PATCH 1489/1650] Directly access class methods in introspection (fixes #966) inspect.signature is unable to process classmethod instances. So we directly access the member via its __get__. --- bpython/inspection.py | 3 +++ bpython/test/test_inspection.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 4268f4fbf..6914e5a6e 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -358,6 +358,9 @@ def getattr_safe(obj: Any, name: str) -> Any: # Slots are a MemberDescriptorType if isinstance(result, MemberDescriptorType): result = getattr(obj, name) + # classmethods are safe to access (see #966) + if isinstance(result, classmethod): + result = result.__get__(obj, obj) return result diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 3cb87d396..43915f3e2 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -180,7 +180,6 @@ def fun_annotations(number: int, lst: List[int] = []) -> List[int]: self.assertEqual(props.argspec.args, ["number", "lst"]) self.assertEqual(props.argspec.defaults[0], []) - @unittest.expectedFailure def test_issue_966_class_method(self): class Issue966(Sequence): @classmethod From 8db689848c68796ff1d3b7cc5e82e597a6cfb2ad Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 18:39:23 +0200 Subject: [PATCH 1490/1650] Update changelog --- CHANGELOG.rst | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b3700a45f..0c12b5fd3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,13 +6,29 @@ Changelog General information: +* More and more type annotations have been added to the bpython code base. +* Some work has been performed to stop relying on blessings. + New features: -* Auto-closing brackets option added. To enable, add `brackets_completion = True` in the bpython config (press F3 to create) + +* #905: Auto-closing brackets option added. To enable, add `brackets_completion = True` in the bpython config Thanks to samuelgregorovic Fixes: -* Support for Python 3.6 has been dropped. +* Improve handling of SyntaxErrors +* #948: Fix crash on Ctrl-Z +* #952: Fix tests for Python 3.10.1 and newer +* #955: Handle optional `readline` parameters in `stdin` emulation + Thanks to thevibingcat +* #959: Fix handling of `__name__` +* #966: Fix function signature completion for `classmethod`s + +Changes to dependencies: + +* curtsies 0.4 or newer is now required + +Support for Python 3.6 has been dropped. 0.22.1 ------ From 265dd4fbb9a3996bbe5d1572ac57ff3145868587 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 18:45:45 +0200 Subject: [PATCH 1491/1650] Fix a typo --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 6914e5a6e..b45d49f9d 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -58,7 +58,7 @@ class AttrCleaner(ContextManager[None]): """A context manager that tries to make an object not exhibit side-effects on attribute lookup. - Unless explicitely required, prefer `getattr_safe`.""" + Unless explicitly required, prefer `getattr_safe`.""" def __init__(self, obj: Any) -> None: self._obj = obj From c079356b426a7c6650ef45d6c2bb4cf63d6348d3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 20:04:45 +0200 Subject: [PATCH 1492/1650] Also directly access staticmethods --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index b45d49f9d..3efacd5b9 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -359,7 +359,7 @@ def getattr_safe(obj: Any, name: str) -> Any: if isinstance(result, MemberDescriptorType): result = getattr(obj, name) # classmethods are safe to access (see #966) - if isinstance(result, classmethod): + if isinstance(result, (classmethod, staticmethod)): result = result.__get__(obj, obj) return result From 424345e0db2dd5024dce3def1079901f0655cccc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 20:04:55 +0200 Subject: [PATCH 1493/1650] Improve parenthesis checks --- bpython/inspection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 3efacd5b9..6b9074c0c 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -138,9 +138,9 @@ def parsekeywordpairs(signature: str) -> Dict[str, str]: continue if token is Token.Punctuation: - if value in ("(", "{", "["): + if value in "({[": parendepth += 1 - elif value in (")", "}", "]"): + elif value in ")}]": parendepth -= 1 elif value == ":" and parendepth == -1: # End of signature reached From 167fc26d4beae59d02235259e8cdec631a633970 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 21:53:39 +0200 Subject: [PATCH 1494/1650] Hide implementation details --- bpython/autocomplete.py | 15 +++++++-------- bpython/test/test_autocomplete.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 782b8b87a..ddb47239e 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -176,11 +176,11 @@ def from_string(cls, value: str) -> Optional["AutocompleteModes"]: KEYWORDS = frozenset(keyword.kwlist) -def after_last_dot(name: str) -> str: +def _after_last_dot(name: str) -> str: return name.rstrip(".").rsplit(".")[-1] -def few_enough_underscores(current: str, match: str) -> bool: +def _few_enough_underscores(current: str, match: str) -> bool: """Returns whether match should be shown based on current if current is _, True if match starts with 0 or 1 underscore @@ -340,7 +340,7 @@ def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: return lineparts.current_word(cursor_offset, line) def format(self, word: str) -> str: - return after_last_dot(word) + return _after_last_dot(word) def _safe_glob(pathname: str) -> Iterator[str]: @@ -409,14 +409,14 @@ def matches( return { m for m in matches - if few_enough_underscores(r.word.split(".")[-1], m.split(".")[-1]) + if _few_enough_underscores(r.word.split(".")[-1], m.split(".")[-1]) } def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: return lineparts.current_dotted_attribute(cursor_offset, line) def format(self, word: str) -> str: - return after_last_dot(word) + return _after_last_dot(word) def attr_matches(self, text: str, namespace: Dict[str, Any]) -> List: """Taken from rlcompleter.py and bent to my will.""" @@ -434,8 +434,7 @@ def attr_matches(self, text: str, namespace: Dict[str, Any]) -> List: obj = safe_eval(expr, namespace) except EvaluationError: return [] - matches = self.attr_lookup(obj, expr, attr) - return matches + return self.attr_lookup(obj, expr, attr) def attr_lookup(self, obj: Any, expr: str, attr: str) -> List: """Second half of attr_matches.""" @@ -631,7 +630,7 @@ def matches( # strips leading dot matches = (m[1:] for m in self.attr_lookup(obj, "", attr.word)) - return {m for m in matches if few_enough_underscores(attr.word, m)} + return {m for m in matches if _few_enough_underscores(attr.word, m)} try: diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index c95328fbb..0000b0b60 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -35,7 +35,7 @@ def test_filename(self): self.assertEqual(last_part_of_filename("ab.c/e.f.g/"), "e.f.g/") def test_attribute(self): - self.assertEqual(autocomplete.after_last_dot("abc.edf"), "edf") + self.assertEqual(autocomplete._after_last_dot("abc.edf"), "edf") def completer(matches): From 0c40b67910c68a7d6bb73bb64c4b5e7235956b3b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 21:53:54 +0200 Subject: [PATCH 1495/1650] Refactor --- bpython/autocomplete.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index ddb47239e..45ae8ffd8 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -715,17 +715,15 @@ def matches( ) -> Optional[Set]: if current_block is None or history is None: return None - - if "\n" in current_block: - assert cursor_offset <= len(line), "{!r} {!r}".format( - cursor_offset, - line, - ) - results = super().matches(cursor_offset, line, history=history) - return results - else: + if "\n" not in current_block: return None + assert cursor_offset <= len(line), "{!r} {!r}".format( + cursor_offset, + line, + ) + return super().matches(cursor_offset, line, history=history) + def get_completer( completers: Sequence[BaseCompletionType], From cd20fd7ddc4f392f6655b49795cb3a39ba9383cc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 21:54:25 +0200 Subject: [PATCH 1496/1650] Remove unused import --- bpython/lazyre.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 8f1e70995..0ca5b9ffa 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -21,7 +21,7 @@ # THE SOFTWARE. import re -from typing import Optional, Iterator, Pattern, Match, Optional +from typing import Optional, Pattern, Match, Optional try: from functools import cached_property From beb1994d0c46453a65a5989df2cadcc5fb9c5165 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 21:53:48 +0200 Subject: [PATCH 1497/1650] Avoid allocation of a list The list is later turned into a set. --- bpython/autocomplete.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 45ae8ffd8..22a514b69 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -418,25 +418,27 @@ def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: def format(self, word: str) -> str: return _after_last_dot(word) - def attr_matches(self, text: str, namespace: Dict[str, Any]) -> List: + def attr_matches( + self, text: str, namespace: Dict[str, Any] + ) -> Iterator[str]: """Taken from rlcompleter.py and bent to my will.""" m = self.attr_matches_re.match(text) if not m: - return [] + return (_ for _ in ()) expr, attr = m.group(1, 3) if expr.isdigit(): # Special case: float literal, using attrs here will result in # a SyntaxError - return [] + return (_ for _ in ()) try: obj = safe_eval(expr, namespace) except EvaluationError: - return [] + return (_ for _ in ()) return self.attr_lookup(obj, expr, attr) - def attr_lookup(self, obj: Any, expr: str, attr: str) -> List: + def attr_lookup(self, obj: Any, expr: str, attr: str) -> Iterator[str]: """Second half of attr_matches.""" words = self.list_attributes(obj) if inspection.hasattr_safe(obj, "__class__"): @@ -449,12 +451,12 @@ def attr_lookup(self, obj: Any, expr: str, attr: str) -> List: except ValueError: pass - matches = [] n = len(attr) - for word in words: - if self.method_match(word, n, attr) and word != "__builtins__": - matches.append(f"{expr}.{word}") - return matches + return ( + f"{expr}.{word}" + for word in words + if self.method_match(word, n, attr) and word != "__builtins__" + ) def list_attributes(self, obj: Any) -> List[str]: # TODO: re-implement dir without AttrCleaner here From 65887e3c91e670523a6b6e8ac59854b4b55cd8f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 22:25:44 +0200 Subject: [PATCH 1498/1650] Fix return type annotations --- bpython/autocomplete.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 22a514b69..575277e7e 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -308,7 +308,7 @@ def format(self, word: str) -> str: def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[Set]: + ) -> Optional[Set[str]]: return_value = None all_matches = set() for completer in self._completers: @@ -333,7 +333,7 @@ def __init__( def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[Set]: + ) -> Optional[Set[str]]: return self.module_gatherer.complete(cursor_offset, line) def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: @@ -353,7 +353,7 @@ def __init__(self, mode: AutocompleteModes = AutocompleteModes.SIMPLE): def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[Set]: + ) -> Optional[Set[str]]: cs = lineparts.current_string(cursor_offset, line) if cs is None: return None @@ -389,7 +389,7 @@ def matches( *, locals_: Optional[Dict[str, Any]] = None, **kwargs: Any, - ) -> Optional[Set]: + ) -> Optional[Set[str]]: r = self.locate(cursor_offset, line) if r is None: return None @@ -474,7 +474,7 @@ def matches( *, locals_: Optional[Dict[str, Any]] = None, **kwargs: Any, - ) -> Optional[Set]: + ) -> Optional[Set[str]]: if locals_ is None: return None @@ -514,7 +514,7 @@ def matches( current_block: Optional[str] = None, complete_magic_methods: Optional[bool] = None, **kwargs: Any, - ) -> Optional[Set]: + ) -> Optional[Set[str]]: if ( current_block is None or complete_magic_methods is None @@ -541,7 +541,7 @@ def matches( *, locals_: Optional[Dict[str, Any]] = None, **kwargs: Any, - ) -> Optional[Set]: + ) -> Optional[Set[str]]: """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace that match. @@ -582,7 +582,7 @@ def matches( *, funcprops: Optional[inspection.FuncProps] = None, **kwargs: Any, - ) -> Optional[Set]: + ) -> Optional[Set[str]]: if funcprops is None: return None @@ -618,7 +618,7 @@ def matches( *, locals_: Optional[Dict[str, Any]] = None, **kwargs: Any, - ) -> Optional[Set]: + ) -> Optional[Set[str]]: if locals_ is None: locals_ = __main__.__dict__ @@ -642,7 +642,7 @@ def matches( class MultilineJediCompletion(BaseCompletionType): # type: ignore [no-redef] def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[Set]: + ) -> Optional[Set[str]]: return None def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: @@ -660,7 +660,7 @@ def matches( *, history: Optional[List[str]] = None, **kwargs: Any, - ) -> Optional[Set]: + ) -> Optional[Set[str]]: if history is None: return None if not lineparts.current_word(cursor_offset, line): @@ -714,7 +714,7 @@ def matches( current_block: Optional[str] = None, history: Optional[List[str]] = None, **kwargs: Any, - ) -> Optional[Set]: + ) -> Optional[Set[str]]: if current_block is None or history is None: return None if "\n" not in current_block: From d533ad2ca29587419b3763584c0b4261a46b580a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 22:39:34 +0200 Subject: [PATCH 1499/1650] Fix some type annotations --- bpython/curtsies.py | 10 +++++----- bpython/simpleeval.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index f7b2ef472..6d289aaa6 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -72,10 +72,10 @@ def __init__( self._request_refresh_callback: Callable[ [], None ] = self.input_generator.event_trigger(events.RefreshRequestEvent) - self._schedule_refresh_callback: Callable[ - [float], None - ] = self.input_generator.scheduled_event_trigger( - events.ScheduledRefreshRequestEvent + self._schedule_refresh_callback = ( + self.input_generator.scheduled_event_trigger( + events.ScheduledRefreshRequestEvent + ) ) self._request_reload_callback = ( self.input_generator.threadsafe_event_trigger(events.ReloadEvent) @@ -252,7 +252,7 @@ def curtsies_arguments(parser: argparse._ArgumentGroup) -> None: def _combined_events( - event_provider: "SupportsEventGeneration", paste_threshold: int + event_provider: SupportsEventGeneration, paste_threshold: int ) -> Generator[Union[str, curtsies.events.Event, None], Optional[float], None]: """Combines consecutive keypress events into paste events.""" timeout = yield "nonsense_event" # so send can be used immediately diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 193a69899..251e5e7f4 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -217,7 +217,7 @@ def find_attribute_with_name(node, name): def evaluate_current_expression( cursor_offset: int, line: str, namespace: Optional[Dict[str, Any]] = None -): +) -> Any: """ Return evaluated expression to the right of the dot of current attribute. From 575796983a6bd77391363b2f045af298188ee009 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 22:39:43 +0200 Subject: [PATCH 1500/1650] Remove unnecessary check --- bpython/simpleeval.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 251e5e7f4..c5bba43db 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -227,9 +227,6 @@ def evaluate_current_expression( # Find the biggest valid ast. # Once our attribute access is found, return its .value subtree - if namespace is None: - namespace = {} - # in case attribute is blank, e.g. foo.| -> foo.xxx| temp_line = line[:cursor_offset] + "xxx" + line[cursor_offset:] temp_cursor = cursor_offset + 3 From cc9cbbf63bca474b2bc1da9f75f8c2127e1b8a76 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 29 Aug 2022 22:54:55 +0200 Subject: [PATCH 1501/1650] Update copyright years --- doc/sphinx/source/conf.py | 121 ++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 57 deletions(-) diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py index 2c5263d90..2ef900498 100644 --- a/doc/sphinx/source/conf.py +++ b/doc/sphinx/source/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # bpython documentation build configuration file, created by # sphinx-quickstart on Mon Jun 8 11:58:16 2009. @@ -16,7 +15,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) +# sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- @@ -25,20 +24,20 @@ extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8' +# source_encoding = 'utf-8' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'bpython' -copyright = u'2008-2021 Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al.' +project = "bpython" +copyright = "2008-2022 Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al." # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -46,172 +45,180 @@ # # The short X.Y version. -version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '../../../bpython/_version.py') +version_file = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "../../../bpython/_version.py" +) with open(version_file) as vf: - version = vf.read().strip().split('=')[-1].replace('\'', '') + version = vf.read().strip().split("=")[-1].replace("'", "") # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. -unused_docs = ['configuration-options'] +unused_docs = ["configuration-options"] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'nature' +html_theme = "nature" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = 'logo.png' +html_logo = "logo.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -html_last_updated_fmt = '%b %d, %Y' +html_last_updated_fmt = "%b %d, %Y" # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +# html_use_modindex = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -html_use_opensearch = '' +html_use_opensearch = "" # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' +# html_file_suffix = '' # Output file base name for HTML help builder. -htmlhelp_basename = 'bpythondoc' +htmlhelp_basename = "bpythondoc" # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +# latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). -#latex_documents = [ +# latex_documents = [ # ('index', 'bpython.tex', u'bpython Documentation', # u'Robert Farrell', 'manual'), -#] +# ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +# latex_use_modindex = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('man-bpython', 'bpython', - u'a fancy {curtsies, curses, urwid} interface to the Python interactive interpreter', - [], 1), - ('man-bpython-config', 'bpython-config', - u'user configuration file for bpython', - [], 5) + ( + "man-bpython", + "bpython", + "a fancy {curtsies, curses, urwid} interface to the Python interactive interpreter", + [], + 1, + ), + ( + "man-bpython-config", + "bpython-config", + "user configuration file for bpython", + [], + 5, + ), ] # If true, show URL addresses after external links. -#man_show_urls = False - +# man_show_urls = False From 5cc2c3f8434d8427c5b574bf2e53d6840afe12cc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 30 Aug 2022 00:01:37 +0200 Subject: [PATCH 1502/1650] Avoid string formatting if not producing any log output --- bpython/args.py | 29 +++++++++++------------------ bpython/autocomplete.py | 7 +++---- bpython/curtsiesfrontend/repl.py | 2 +- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 1212fe3f6..ec7d3b299 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -204,24 +204,17 @@ def callback(group): bpython_logger.addHandler(logging.NullHandler()) curtsies_logger.addHandler(logging.NullHandler()) - logger.info(f"Starting bpython {__version__}") - logger.info(f"Python {sys.executable}: {sys.version_info}") - logger.info(f"curtsies: {curtsies.__version__}") - logger.info(f"cwcwidth: {cwcwidth.__version__}") - logger.info(f"greenlet: {greenlet.__version__}") - logger.info(f"pygments: {pygments.__version__}") # type: ignore - logger.info(f"requests: {requests.__version__}") - logger.info( - "environment:\n{}".format( - "\n".join( - f"{key}: {value}" - for key, value in sorted(os.environ.items()) - if key.startswith("LC") - or key.startswith("LANG") - or key == "TERM" - ) - ) - ) + logger.info("Starting bpython %s", __version__) + logger.info("Python %s: %s", sys.executable, sys.version_info) + logger.info("curtsies: %s", curtsies.__version__) + logger.info("cwcwidth: %s", cwcwidth.__version__) + logger.info("greenlet: %s", greenlet.__version__) + logger.info("pygments: %s", pygments.__version__) # type: ignore + logger.info("requests: %s", requests.__version__) + logger.info("environment:") + for key, value in sorted(os.environ.items()): + if key.startswith("LC") or key.startswith("LANG") or key == "TERM": + logger.info("%s: %s", key, value) return Config(options.config), options, options.args diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 575277e7e..38ffb99d2 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -55,6 +55,8 @@ from .importcompletion import ModuleGatherer +logger = logging.getLogger(__name__) + # Autocomplete modes class AutocompleteModes(Enum): NONE = "none" @@ -767,11 +769,8 @@ def get_completer( ) except Exception as e: # Instead of crashing the UI, log exceptions from autocompleters. - logger = logging.getLogger(__name__) logger.debug( - "Completer {} failed with unhandled exception: {}".format( - completer, e - ) + "Completer %r failed with unhandled exception: %s", completer, e ) continue if matches is not None: diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index ef7081617..b950be68c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1005,7 +1005,7 @@ def only_whitespace_left_of_cursor(): """returns true if all characters before cursor are whitespace""" return not self.current_line[: self.cursor_offset].strip() - logger.debug("self.matches_iter.matches:%r", self.matches_iter.matches) + logger.debug("self.matches_iter.matches: %r", self.matches_iter.matches) if only_whitespace_left_of_cursor(): front_ws = len(self.current_line[: self.cursor_offset]) - len( self.current_line[: self.cursor_offset].lstrip() From e5e4aef17529c1ef033652a0812b3d1bc69f964f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 30 Aug 2022 22:03:59 +0200 Subject: [PATCH 1503/1650] Start development of 0.24 --- CHANGELOG.rst | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0c12b5fd3..b9a4c8b55 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,22 @@ Changelog ========= +0.24 +---- + +General information: + + +New features: + + +Fixes: + + +Changes to dependencies: + + + 0.23 ---- @@ -22,7 +38,7 @@ Fixes: * #955: Handle optional `readline` parameters in `stdin` emulation Thanks to thevibingcat * #959: Fix handling of `__name__` -* #966: Fix function signature completion for `classmethod`s +* #966: Fix function signature completion for `classmethod` Changes to dependencies: From e710dfe6aaf8fa252e6a68ae138500fcab897828 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 31 Aug 2022 12:51:11 +0200 Subject: [PATCH 1504/1650] Inject subcommands into build_py --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 12d4eeec5..7d1a67703 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ import subprocess from setuptools import setup -from distutils.command.build import build +from distutils.command.build_py import build_py try: from babel.messages import frontend as babel @@ -122,7 +122,7 @@ def git_describe_to_python_version(version): vf.write(f'__version__ = "{version}"\n') -cmdclass = {"build": build} +cmdclass = {"build_py": build_py} from bpython import package_dir, __author__ @@ -130,7 +130,7 @@ def git_describe_to_python_version(version): # localization options if using_translations: - build.sub_commands.insert(0, ("compile_catalog", None)) + build_py.sub_commands.insert(0, ("compile_catalog", None)) cmdclass["compile_catalog"] = babel.compile_catalog cmdclass["extract_messages"] = babel.extract_messages @@ -138,7 +138,7 @@ def git_describe_to_python_version(version): cmdclass["init_catalog"] = babel.init_catalog if using_sphinx: - build.sub_commands.insert(0, ("build_sphinx_man", None)) + build_py.sub_commands.insert(0, ("build_sphinx_man", None)) cmdclass["build_sphinx_man"] = BuildDoc if platform.system() in ("FreeBSD", "OpenBSD"): From 0bce729bcc4c84b256502fd9de0bba16117187c5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 31 Aug 2022 22:01:06 +0200 Subject: [PATCH 1505/1650] Revert "Inject subcommands into build_py" This reverts commit e710dfe6aaf8fa252e6a68ae138500fcab897828. --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 7d1a67703..12d4eeec5 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ import subprocess from setuptools import setup -from distutils.command.build_py import build_py +from distutils.command.build import build try: from babel.messages import frontend as babel @@ -122,7 +122,7 @@ def git_describe_to_python_version(version): vf.write(f'__version__ = "{version}"\n') -cmdclass = {"build_py": build_py} +cmdclass = {"build": build} from bpython import package_dir, __author__ @@ -130,7 +130,7 @@ def git_describe_to_python_version(version): # localization options if using_translations: - build_py.sub_commands.insert(0, ("compile_catalog", None)) + build.sub_commands.insert(0, ("compile_catalog", None)) cmdclass["compile_catalog"] = babel.compile_catalog cmdclass["extract_messages"] = babel.extract_messages @@ -138,7 +138,7 @@ def git_describe_to_python_version(version): cmdclass["init_catalog"] = babel.init_catalog if using_sphinx: - build_py.sub_commands.insert(0, ("build_sphinx_man", None)) + build.sub_commands.insert(0, ("build_sphinx_man", None)) cmdclass["build_sphinx_man"] = BuildDoc if platform.system() in ("FreeBSD", "OpenBSD"): From e355191bcb090626e352c7bce8d1744d381e5d86 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 31 Aug 2022 22:14:03 +0200 Subject: [PATCH 1506/1650] Add config for readthedocs.io --- .readthedocs.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..ced748ce7 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,8 @@ +version: 2 +build: + tools: + python: "3.10" + +python: + install: + method: pip From 5e46aaf78e0d68c5efc20b190191f22d882bb91a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 31 Aug 2022 22:15:43 +0200 Subject: [PATCH 1507/1650] Fix readthedocs config --- .readthedocs.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index ced748ce7..01c356be4 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -5,4 +5,5 @@ build: python: install: - method: pip + - method: pip + path: . From 6badea563dd27cc00a20edf0b1f6bfeaf9f904c8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 31 Aug 2022 22:18:39 +0200 Subject: [PATCH 1508/1650] Fix readthedocs config --- .readthedocs.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 01c356be4..942c1da0b 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,7 +1,4 @@ version: 2 -build: - tools: - python: "3.10" python: install: From a03e3457bef05b89b8caa5720263aac72f21c9f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 14 Sep 2022 17:49:52 +0200 Subject: [PATCH 1509/1650] Refactor --- bpython/importcompletion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 969361447..3496a24e5 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -240,9 +240,9 @@ def find_all_modules( self.modules.add(module) yield - def find_coroutine(self) -> Optional[bool]: + def find_coroutine(self) -> bool: if self.fully_loaded: - return None + return False try: next(self.find_iterator) From f1a5cfb195987ff8b647b3066b134dbef60fc0e9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 14 Sep 2022 17:49:59 +0200 Subject: [PATCH 1510/1650] Fix typo --- bpython/inspection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 6b9074c0c..2fd8259b7 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -154,7 +154,7 @@ def parsekeywordpairs(signature: str) -> Dict[str, str]: ): stack.append(substack) substack = [] - # If type annotation didn't end before, ti does now. + # If type annotation didn't end before, it does now. annotation = False continue elif token is Token.Operator and value == "=" and parendepth == 0: From 89784501732975afb9768acb9562e4e113999bd9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 14 Sep 2022 19:30:31 +0200 Subject: [PATCH 1511/1650] Refactor --- bpython/patch_linecache.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index 82b38dd74..d91392d24 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -1,21 +1,24 @@ import linecache -from typing import Any, List, Tuple +from typing import Any, List, Tuple, Optional class BPythonLinecache(dict): """Replaces the cache dict in the standard-library linecache module, to also remember (in an unerasable way) bpython console input.""" - def __init__(self, *args, **kwargs) -> None: + def __init__( + self, + bpython_history: Optional[ + List[Tuple[int, None, List[str], str]] + ] = None, + *args, + **kwargs, + ) -> None: super().__init__(*args, **kwargs) - self.bpython_history: List[Tuple[int, None, List[str], str]] = [] + self.bpython_history = bpython_history or [] def is_bpython_filename(self, fname: Any) -> bool: - if isinstance(fname, str): - return fname.startswith(" Tuple[int, None, List[str], str]: """Given a filename provided by remember_bpython_input, @@ -58,14 +61,13 @@ def _bpython_clear_linecache() -> None: if isinstance(linecache.cache, BPythonLinecache): bpython_history = linecache.cache.bpython_history else: - bpython_history = [] - linecache.cache = BPythonLinecache() - linecache.cache.bpython_history = bpython_history + bpython_history = None + linecache.cache = BPythonLinecache(bpython_history) -# Monkey-patch the linecache module so that we're able +# Monkey-patch the linecache module so that we are able # to hold our command history there and have it persist -linecache.cache = BPythonLinecache(linecache.cache) # type: ignore +linecache.cache = BPythonLinecache(None, linecache.cache) # type: ignore linecache.clearcache = _bpython_clear_linecache From 7e64b0f41233761a800f9a453996198361afb47b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Sep 2022 20:34:12 +0200 Subject: [PATCH 1512/1650] Use Optional --- bpython/importcompletion.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 3496a24e5..3e95500eb 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -155,9 +155,7 @@ def complete(self, cursor_offset: int, line: str) -> Optional[Set[str]]: else: return None - def find_modules( - self, path: Path - ) -> Generator[Union[str, None], None, None]: + def find_modules(self, path: Path) -> Generator[Optional[str], None, None]: """Find all modules (and packages) for a given directory.""" if not path.is_dir(): # Perhaps a zip file From ba026ec19e252f2494439dc4b367f0345d09d0a2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Sep 2022 22:32:05 +0200 Subject: [PATCH 1513/1650] Store device id and inodes in a dataclass --- bpython/importcompletion.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 3e95500eb..44062248b 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -25,8 +25,9 @@ import importlib.machinery import sys import warnings +from dataclasses import dataclass from pathlib import Path -from typing import Optional, Set, Generator, Tuple, Sequence, Iterable, Union +from typing import Optional, Set, Generator, Sequence, Iterable, Union from .line import ( current_word, @@ -47,6 +48,16 @@ ), ) +_LOADED_INODE_DATACLASS_ARGS = {"frozen": True} +if sys.version_info[2:] >= (3, 10): + _LOADED_INODE_DATACLASS_ARGS["slots"] = True + + +@dataclass(**_LOADED_INODE_DATACLASS_ARGS) +class _LoadedInode: + dev: int + inode: int + class ModuleGatherer: def __init__( @@ -60,7 +71,7 @@ def __init__( # Cached list of all known modules self.modules: Set[str] = set() # Set of (st_dev, st_ino) to compare against so that paths are not repeated - self.paths: Set[Tuple[int, int]] = set() + self.paths: Set[_LoadedInode] = set() # Patterns to skip self.skiplist: Sequence[str] = ( skiplist if skiplist is not None else tuple() @@ -216,8 +227,9 @@ def find_modules(self, path: Path) -> Generator[Optional[str], None, None]: stat = path_real.stat() except OSError: continue - if (stat.st_dev, stat.st_ino) not in self.paths: - self.paths.add((stat.st_dev, stat.st_ino)) + loaded_inode = _LoadedInode(stat.st_dev, stat.st_ino) + if loaded_inode not in self.paths: + self.paths.add(loaded_inode) for subname in self.find_modules(path_real): if subname is None: yield None # take a break to avoid unresponsiveness From 2868ab10e4afaf36e5d7cdd1e72c856004edece6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Sep 2022 22:44:12 +0200 Subject: [PATCH 1514/1650] Fix typo --- bpython/importcompletion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 44062248b..13a77ab0c 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -49,7 +49,7 @@ ) _LOADED_INODE_DATACLASS_ARGS = {"frozen": True} -if sys.version_info[2:] >= (3, 10): +if sys.version_info[:2] >= (3, 10): _LOADED_INODE_DATACLASS_ARGS["slots"] = True From 900a273ea27a72b6202a087c6d5893daa54f261f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Sep 2022 23:08:39 +0200 Subject: [PATCH 1515/1650] Refactor --- bpython/importcompletion.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 13a77ab0c..398298566 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -206,23 +206,22 @@ def find_modules(self, path: Path) -> Generator[Optional[str], None, None]: # Workaround for issue #166 continue try: - is_package = False + package_pathname = None with warnings.catch_warnings(): warnings.simplefilter("ignore", ImportWarning) spec = finder.find_spec(name) if spec is None: continue if spec.submodule_search_locations is not None: - pathname = spec.submodule_search_locations[0] - is_package = True + package_pathname = spec.submodule_search_locations[0] except (ImportError, OSError, SyntaxError): continue except UnicodeEncodeError: # Happens with Python 3 when there is a filename in some invalid encoding continue else: - if is_package: - path_real = Path(pathname).resolve() + if package_pathname is not None: + path_real = Path(package_pathname).resolve() try: stat = path_real.stat() except OSError: From 3944fa7c8a3cc5549dfb2680bb621b001d995586 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Sep 2022 23:09:00 +0200 Subject: [PATCH 1516/1650] Shortcircuit some paths --- bpython/importcompletion.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 398298566..d099c2a9b 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -186,7 +186,10 @@ def find_modules(self, path: Path) -> Generator[Optional[str], None, None]: finder = importlib.machinery.FileFinder(str(path), *LOADERS) # type: ignore for p in children: - if any(fnmatch.fnmatch(p.name, entry) for entry in self.skiplist): + if p.name.startswith(".") or p.name == "__pycache__": + # Impossible to import from names starting with . and we can skip __pycache__ + continue + elif any(fnmatch.fnmatch(p.name, entry) for entry in self.skiplist): # Path is on skiplist continue elif not any(p.name.endswith(suffix) for suffix in SUFFIXES): From 074a015fde32475916faf5454258a3e71dae0c01 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Sep 2022 23:09:05 +0200 Subject: [PATCH 1517/1650] Refactor --- bpython/importcompletion.py | 45 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index d099c2a9b..c1e073f8e 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -83,7 +83,7 @@ def __init__( paths = sys.path self.find_iterator = self.find_all_modules( - (Path(p).resolve() if p else Path.cwd() for p in paths) + Path(p).resolve() if p else Path.cwd() for p in paths ) def module_matches(self, cw: str, prefix: str = "") -> Set[str]: @@ -120,7 +120,7 @@ def attr_matches( matches = { name for name in dir(module) if name.startswith(name_after_dot) } - module_part, _, _ = cw.rpartition(".") + module_part = cw.rpartition(".")[0] if module_part: matches = {f"{module_part}.{m}" for m in matches} @@ -208,8 +208,9 @@ def find_modules(self, path: Path) -> Generator[Optional[str], None, None]: if name == "badsyntax_pep3120": # Workaround for issue #166 continue + + package_pathname = None try: - package_pathname = None with warnings.catch_warnings(): warnings.simplefilter("ignore", ImportWarning) spec = finder.find_spec(name) @@ -217,27 +218,25 @@ def find_modules(self, path: Path) -> Generator[Optional[str], None, None]: continue if spec.submodule_search_locations is not None: package_pathname = spec.submodule_search_locations[0] - except (ImportError, OSError, SyntaxError): - continue - except UnicodeEncodeError: - # Happens with Python 3 when there is a filename in some invalid encoding + except (ImportError, OSError, SyntaxError, UnicodeEncodeError): + # UnicodeEncodeError happens with Python 3 when there is a filename in some invalid encoding continue - else: - if package_pathname is not None: - path_real = Path(package_pathname).resolve() - try: - stat = path_real.stat() - except OSError: - continue - loaded_inode = _LoadedInode(stat.st_dev, stat.st_ino) - if loaded_inode not in self.paths: - self.paths.add(loaded_inode) - for subname in self.find_modules(path_real): - if subname is None: - yield None # take a break to avoid unresponsiveness - elif subname != "__init__": - yield f"{name}.{subname}" - yield name + + if package_pathname is not None: + path_real = Path(package_pathname).resolve() + try: + stat = path_real.stat() + except OSError: + continue + loaded_inode = _LoadedInode(stat.st_dev, stat.st_ino) + if loaded_inode not in self.paths: + self.paths.add(loaded_inode) + for subname in self.find_modules(path_real): + if subname is None: + yield None # take a break to avoid unresponsiveness + elif subname != "__init__": + yield f"{name}.{subname}" + yield name yield None # take a break to avoid unresponsiveness def find_all_modules( From bb8712c6185b43fd60e351b6702fb23b8d6757d0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Sep 2022 23:25:32 +0200 Subject: [PATCH 1518/1650] Remove unused import --- bpython/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/history.py b/bpython/history.py index a870d4b29..13dbb5b7f 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -25,7 +25,7 @@ from pathlib import Path import stat from itertools import islice, chain -from typing import Iterable, Optional, List, TextIO, Union +from typing import Iterable, Optional, List, TextIO from .translations import _ from .filelock import FileLock From 748ce36c3ab8f93ce80623e223ff8cde4a61b94a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Sep 2022 23:29:05 +0200 Subject: [PATCH 1519/1650] Fix exception handling --- bpython/pager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/pager.py b/bpython/pager.py index 673e902bc..e145e0ed8 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -63,7 +63,6 @@ def page(data: str, use_internal: bool = False) -> None: # pager command not found, fall back to internal pager page_internal(data) return - except OSError as e: if e.errno != errno.EPIPE: raise while True: From f44b8c22eb8929267cc3599f0f84776453ce5e16 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 25 Sep 2022 23:43:58 +0200 Subject: [PATCH 1520/1650] Refactor --- bpython/autocomplete.py | 2 +- bpython/inspection.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 38ffb99d2..69e6e2a2d 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -598,7 +598,7 @@ def matches( if isinstance(name, str) and name.startswith(r.word) } matches.update( - name + "=" + f"{name}=" for name in funcprops.argspec.kwonly if name.startswith(r.word) ) diff --git a/bpython/inspection.py b/bpython/inspection.py index 2fd8259b7..78bbc5782 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -142,16 +142,16 @@ def parsekeywordpairs(signature: str) -> Dict[str, str]: parendepth += 1 elif value in ")}]": parendepth -= 1 - elif value == ":" and parendepth == -1: - # End of signature reached - break - elif value == ":" and parendepth == 0: - # Start of type annotation - annotation = True - - if (value == "," and parendepth == 0) or ( - value == ")" and parendepth == -1 - ): + elif value == ":": + if parendepth == -1: + # End of signature reached + break + elif parendepth == 0: + # Start of type annotation + annotation = True + + if (value, parendepth) in ((",", 0), (")", -1)): + # End of current argument stack.append(substack) substack = [] # If type annotation didn't end before, it does now. From 9281dccb83c3cecfebfd557518f1b87640808d8d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 26 Sep 2022 22:21:38 +0200 Subject: [PATCH 1521/1650] Refactor --- bpython/autocomplete.py | 52 +++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 69e6e2a2d..b97fd86f7 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -555,11 +555,10 @@ def matches( if r is None: return None - matches = set() n = len(r.word) - for word in KEYWORDS: - if self.method_match(word, n, r.word): - matches.add(word) + matches = { + word for word in KEYWORDS if self.method_match(word, n, r.word) + } for nspace in (builtins.__dict__, locals_): for word, val in nspace.items(): # if identifier isn't ascii, don't complete (syntax error) @@ -652,7 +651,7 @@ def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: else: - class JediCompletion(BaseCompletionType): + class MultilineJediCompletion(BaseCompletionType): # type: ignore [no-redef] _orig_start: Optional[int] def matches( @@ -660,19 +659,28 @@ def matches( cursor_offset: int, line: str, *, + current_block: Optional[str] = None, history: Optional[List[str]] = None, **kwargs: Any, ) -> Optional[Set[str]]: - if history is None: - return None - if not lineparts.current_word(cursor_offset, line): + if ( + current_block is None + or history is None + or "\n" not in current_block + or not lineparts.current_word(cursor_offset, line) + ): return None + assert cursor_offset <= len(line), "{!r} {!r}".format( + cursor_offset, + line, + ) + combined_history = "\n".join(itertools.chain(history, (line,))) try: script = jedi.Script(combined_history, path="fake.py") completions = script.complete( - len(combined_history.splitlines()), cursor_offset + combined_history.count("\n") + 1, cursor_offset ) except (jedi.NotFoundError, IndexError, KeyError): # IndexError for #483 @@ -688,8 +696,6 @@ def matches( return None assert isinstance(self._orig_start, int) - first_letter = line[self._orig_start : self._orig_start + 1] - matches = [c.name for c in completions] if any( not m.lower().startswith(matches[0][0].lower()) for m in matches @@ -699,35 +705,15 @@ def matches( return None else: # case-sensitive matches only + first_letter = line[self._orig_start] return {m for m in matches if m.startswith(first_letter)} def locate(self, cursor_offset: int, line: str) -> LinePart: - assert isinstance(self._orig_start, int) + assert self._orig_start is not None start = self._orig_start end = cursor_offset return LinePart(start, end, line[start:end]) - class MultilineJediCompletion(JediCompletion): # type: ignore [no-redef] - def matches( - self, - cursor_offset: int, - line: str, - *, - current_block: Optional[str] = None, - history: Optional[List[str]] = None, - **kwargs: Any, - ) -> Optional[Set[str]]: - if current_block is None or history is None: - return None - if "\n" not in current_block: - return None - - assert cursor_offset <= len(line), "{!r} {!r}".format( - cursor_offset, - line, - ) - return super().matches(cursor_offset, line, history=history) - def get_completer( completers: Sequence[BaseCompletionType], From 1c6f1813dc8c6c8ac24daa8916a0a8d5a3aab9a1 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 19 Oct 2022 19:43:54 +0200 Subject: [PATCH 1522/1650] Fix deprecation warning --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 748ce965e..35806eca1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ name = bpython long_description = file: README.rst license = MIT -license_file = LICENSE +license_files = LICENSE url = https://www.bpython-interpreter.org/ project_urls = GitHub = https://github.com/bpython/bpython From d47c2387f5d173e0175a95c782c79414c7ddd5ac Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 19 Oct 2022 20:32:02 +0200 Subject: [PATCH 1523/1650] GA: checkspell: ignore dedented --- .github/workflows/lint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 839681b92..57aad1bda 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -25,7 +25,7 @@ jobs: - uses: codespell-project/actions-codespell@master with: skip: '*.po' - ignore_words_list: ba,te,deltion + ignore_words_list: ba,te,deltion,dedent,dedented mypy: runs-on: ubuntu-latest From 4759bcb766c633b269d75a4036c8885d45940d29 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 25 Oct 2022 15:01:41 +0200 Subject: [PATCH 1524/1650] GA: test with Python 3.11 --- .github/workflows/build.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 89edab7c8..c16bcd70e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -14,7 +14,13 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, "3.10", "pypy-3.7"] + python-version: + - "3.7" + - "3.8" + - "3.9", + - "3.10", + - "3.11", + - "pypy-3.7" steps: - uses: actions/checkout@v2 with: From 3e495488af4a66a8be267a588919ac1722a9b9b7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 25 Oct 2022 15:03:24 +0200 Subject: [PATCH 1525/1650] GA: fix syntax --- .github/workflows/build.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c16bcd70e..e9a3e1626 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -17,9 +17,9 @@ jobs: python-version: - "3.7" - "3.8" - - "3.9", - - "3.10", - - "3.11", + - "3.9" + - "3.10" + - "3.11" - "pypy-3.7" steps: - uses: actions/checkout@v2 From f2ad2a1e771d48e6ab1280d9c75e42381bf10993 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 26 Oct 2022 15:34:09 +0200 Subject: [PATCH 1526/1650] GA: update actions with dependabot --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..8c139c7be --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" From 82389987bfb1a4fa46e18b70882e5e909a9e82a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 13:34:35 +0000 Subject: [PATCH 1527/1650] Bump actions/setup-python from 2 to 4 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 4. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 2 +- .github/workflows/lint.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e9a3e1626..faeb9c8f3 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -26,7 +26,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 57aad1bda..b24cd6e56 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -32,7 +32,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 - name: Install dependencies run: | python -m pip install --upgrade pip From ec2f03354e115d61d273384c3ba7bbcff366c1bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 13:36:47 +0000 Subject: [PATCH 1528/1650] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 2 +- .github/workflows/lint.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index faeb9c8f3..052969b8e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,7 +22,7 @@ jobs: - "3.11" - "pypy-3.7" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index b24cd6e56..f644f5434 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -8,7 +8,7 @@ jobs: black: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 - name: Install dependencies @@ -21,7 +21,7 @@ jobs: codespell: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: codespell-project/actions-codespell@master with: skip: '*.po' @@ -30,7 +30,7 @@ jobs: mypy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 - name: Install dependencies From 9e32826795944c8b97092077164bae91045ff0a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 13:34:46 +0000 Subject: [PATCH 1529/1650] Bump codecov/codecov-action from 1 to 3 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1 to 3. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v1...v3) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 052969b8e..9cdcc1e92 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -46,7 +46,7 @@ jobs: run: | pytest --cov=bpython --cov-report=xml -v - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 env: PYTHON_VERSION: ${{ matrix.python-version }} with: From 742085633868c72e5a42ce452f3deea57de442fc Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 26 Oct 2022 16:19:04 +0200 Subject: [PATCH 1530/1650] Handle changed output in Python 3.11 --- bpython/test/test_interpreter.py | 42 ++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 65b60a925..f53ec2529 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -112,19 +112,35 @@ def gfunc(): global_not_found = "name 'gfunc' is not defined" - expected = ( - "Traceback (most recent call last):\n File " - + green('""') - + ", line " - + bold(magenta("1")) - + ", in " - + cyan("") - + "\n gfunc()\n" - + bold(red("NameError")) - + ": " - + cyan(global_not_found) - + "\n" - ) + if (3, 11) <= sys.version_info[:2]: + expected = ( + "Traceback (most recent call last):\n File " + + green('""') + + ", line " + + bold(magenta("1")) + + ", in " + + cyan("") + + "\n gfunc()" + + "\n ^^^^^\n" + + bold(red("NameError")) + + ": " + + cyan(global_not_found) + + "\n" + ) + else: + expected = ( + "Traceback (most recent call last):\n File " + + green('""') + + ", line " + + bold(magenta("1")) + + ", in " + + cyan("") + + "\n gfunc()\n" + + bold(red("NameError")) + + ": " + + cyan(global_not_found) + + "\n" + ) self.assertMultiLineEqual(str(plain("").join(a)), str(expected)) self.assertEqual(plain("").join(a), expected) From b04ad8835c6eb777393536efa4e89045a3883852 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 26 Oct 2022 17:48:57 +0200 Subject: [PATCH 1531/1650] mypy: ignore import errors from twisted --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 35806eca1..7955ee390 100644 --- a/setup.cfg +++ b/setup.cfg @@ -77,3 +77,6 @@ ignore_missing_imports = True [mypy-urwid] ignore_missing_imports = True + +[mypy-twisted.*] +ignore_missing_imports = True From 3b8927852e84fd6581c83ef974920856d5f3fb47 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 26 Oct 2022 19:21:58 +0200 Subject: [PATCH 1532/1650] Refactor Also, as we have many small _Repr instances, make the value a slot. --- bpython/inspection.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 78bbc5782..8ae5c20a0 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -36,6 +36,22 @@ from .lazyre import LazyReCompile +class _Repr: + """ + Helper for `ArgSpec`: Returns the given value in `__repr__()`. + """ + + __slots__ = ("value",) + + def __init__(self, value: str) -> None: + self.value = value + + def __repr__(self) -> str: + return self.value + + __str__ = __repr__ + + @dataclass class ArgSpec: args: List[str] @@ -110,20 +126,6 @@ def __exit__( return False -class _Repr: - """ - Helper for `fixlongargs()`: Returns the given value in `__repr__()`. - """ - - def __init__(self, value: str) -> None: - self.value = value - - def __repr__(self) -> str: - return self.value - - __str__ = __repr__ - - def parsekeywordpairs(signature: str) -> Dict[str, str]: preamble = True stack = [] @@ -293,12 +295,15 @@ def _get_argspec_from_signature(f: Callable) -> ArgSpec: """ args = [] - varargs = varkwargs = None + varargs = None + varkwargs = None defaults = [] kwonly = [] kwonly_defaults = {} annotations = {} + # We use signature here instead of getfullargspec as the latter also returns + # self and cls (for class methods). signature = inspect.signature(f) for parameter in signature.parameters.values(): if parameter.annotation is not parameter.empty: From 917382530f0de38e775312cacfab5ebe498502a3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 26 Oct 2022 19:56:27 +0200 Subject: [PATCH 1533/1650] Fix inspection of built-in functions with >= 3.11 The built-in functions no longer have their signature in the docstring, but now inspect.signature can produce results. But as we have no source for built-in functions, we cannot replace the default values. Hence, we handle built-in functions in an extra step. This commit also changes the handling of default values slightly. They are now always put into a _Repr. --- bpython/inspection.py | 60 ++++++++++++++++++++++----------- bpython/test/test_inspection.py | 36 ++++++++++---------- 2 files changed, 60 insertions(+), 36 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 8ae5c20a0..e1e4ed8db 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -57,9 +57,9 @@ class ArgSpec: args: List[str] varargs: Optional[str] varkwargs: Optional[str] - defaults: Optional[List[Any]] + defaults: Optional[List[_Repr]] kwonly: List[str] - kwonly_defaults: Optional[Dict[str, Any]] + kwonly_defaults: Optional[Dict[str, _Repr]] annotations: Optional[Dict[str, Any]] @@ -169,31 +169,51 @@ def parsekeywordpairs(signature: str) -> Dict[str, str]: return {item[0]: "".join(item[2:]) for item in stack if len(item) >= 3} -def _fixlongargs(f: Callable, argspec: ArgSpec) -> ArgSpec: +def _fix_default_values(f: Callable, argspec: ArgSpec) -> ArgSpec: """Functions taking default arguments that are references to other objects - whose str() is too big will cause breakage, so we swap out the object - itself with the name it was referenced with in the source by parsing the - source itself !""" - if argspec.defaults is None: + will cause breakage, so we swap out the object itself with the name it was + referenced with in the source by parsing the source itself!""" + + if argspec.defaults is None and argspec.kwonly_defaults is None: # No keyword args, no need to do anything return argspec - values = list(argspec.defaults) - if not values: - return argspec - keys = argspec.args[-len(values) :] + try: - src = inspect.getsourcelines(f) + src, _ = inspect.getsourcelines(f) except (OSError, IndexError): # IndexError is raised in inspect.findsource(), can happen in # some situations. See issue #94. return argspec - kwparsed = parsekeywordpairs("".join(src[0])) + except TypeError: + # No source code is available (for Python >= 3.11) + # + # If the function is a builtin, we replace the default values. + # Otherwise, let's bail out. + if not inspect.isbuiltin(f): + raise + + if argspec.defaults is not None: + argspec.defaults = [_Repr(str(value)) for value in argspec.defaults] + if argspec.kwonly_defaults is not None: + argspec.kwonly_defaults = { + key: _Repr(str(value)) + for key, value in argspec.kwonly_defaults.items() + } + return argspec - for i, (key, value) in enumerate(zip(keys, values)): - if len(repr(value)) != len(kwparsed[key]): + kwparsed = parsekeywordpairs("".join(src)) + + if argspec.defaults is not None: + values = list(argspec.defaults) + keys = argspec.args[-len(values) :] + for i, key in enumerate(keys): values[i] = _Repr(kwparsed[key]) - argspec.defaults = values + argspec.defaults = values + if argspec.kwonly_defaults is not None: + for key in argspec.kwonly_defaults.keys(): + argspec.kwonly_defaults[key] = _Repr(kwparsed[key]) + return argspec @@ -234,11 +254,11 @@ def _getpydocspec(f: Callable) -> Optional[ArgSpec]: if varargs is not None: kwonly_args.append(arg) if default: - kwonly_defaults[arg] = default + kwonly_defaults[arg] = _Repr(default) else: args.append(arg) if default: - defaults.append(default) + defaults.append(_Repr(default)) return ArgSpec( args, varargs, varkwargs, defaults, kwonly_args, kwonly_defaults, None @@ -267,7 +287,9 @@ def getfuncprops(func: str, f: Callable) -> Optional[FuncProps]: return None try: argspec = _get_argspec_from_signature(f) - fprops = FuncProps(func, _fixlongargs(f, argspec), is_bound_method) + fprops = FuncProps( + func, _fix_default_values(f, argspec), is_bound_method + ) except (TypeError, KeyError, ValueError): argspec_pydoc = _getpydocspec(f) if argspec_pydoc is None: diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 43915f3e2..3f04222de 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -11,6 +11,7 @@ from bpython.test.fodder import encoding_utf8 pypy = "PyPy" in sys.version +_is_py311 = sys.version_info[:2] >= (3, 11) try: import numpy @@ -53,23 +54,17 @@ def test_parsekeywordpairs(self): def fails(spam=["-a", "-b"]): pass - default_arg_repr = "['-a', '-b']" - self.assertEqual( - str(["-a", "-b"]), - default_arg_repr, - "This test is broken (repr does not match), fix me.", - ) - argspec = inspection.getfuncprops("fails", fails) + self.assertIsNotNone(argspec) defaults = argspec.argspec.defaults - self.assertEqual(str(defaults[0]), default_arg_repr) + self.assertEqual(str(defaults[0]), '["-a", "-b"]') def test_pasekeywordpairs_string(self): def spam(eggs="foo, bar"): pass defaults = inspection.getfuncprops("spam", spam).argspec.defaults - self.assertEqual(repr(defaults[0]), "'foo, bar'") + self.assertEqual(repr(defaults[0]), '"foo, bar"') def test_parsekeywordpairs_multiple_keywords(self): def spam(eggs=23, foobar="yay"): @@ -77,14 +72,14 @@ def spam(eggs=23, foobar="yay"): defaults = inspection.getfuncprops("spam", spam).argspec.defaults self.assertEqual(repr(defaults[0]), "23") - self.assertEqual(repr(defaults[1]), "'yay'") + self.assertEqual(repr(defaults[1]), '"yay"') def test_pasekeywordpairs_annotation(self): def spam(eggs: str = "foo, bar"): pass defaults = inspection.getfuncprops("spam", spam).argspec.defaults - self.assertEqual(repr(defaults[0]), "'foo, bar'") + self.assertEqual(repr(defaults[0]), '"foo, bar"') def test_get_encoding_ascii(self): self.assertEqual(inspection.get_encoding(encoding_ascii), "ascii") @@ -134,8 +129,15 @@ def test_getfuncprops_print(self): self.assertIn("file", props.argspec.kwonly) self.assertIn("flush", props.argspec.kwonly) self.assertIn("sep", props.argspec.kwonly) - self.assertEqual(props.argspec.kwonly_defaults["file"], "sys.stdout") - self.assertEqual(props.argspec.kwonly_defaults["flush"], "False") + if _is_py311: + self.assertEqual( + repr(props.argspec.kwonly_defaults["file"]), "None" + ) + else: + self.assertEqual( + repr(props.argspec.kwonly_defaults["file"]), "sys.stdout" + ) + self.assertEqual(repr(props.argspec.kwonly_defaults["flush"]), "False") @unittest.skipUnless( numpy is not None and numpy.__version__ >= "1.18", @@ -173,12 +175,12 @@ def fun_annotations(number: int, lst: List[int] = []) -> List[int]: props = inspection.getfuncprops("fun", fun) self.assertEqual(props.func, "fun") self.assertEqual(props.argspec.args, ["number", "lst"]) - self.assertEqual(props.argspec.defaults[0], []) + self.assertEqual(repr(props.argspec.defaults[0]), "[]") props = inspection.getfuncprops("fun_annotations", fun_annotations) self.assertEqual(props.func, "fun_annotations") self.assertEqual(props.argspec.args, ["number", "lst"]) - self.assertEqual(props.argspec.defaults[0], []) + self.assertEqual(repr(props.argspec.defaults[0]), "[]") def test_issue_966_class_method(self): class Issue966(Sequence): @@ -215,7 +217,7 @@ def bmethod(cls, number, lst): ) self.assertEqual(props.func, "cmethod") self.assertEqual(props.argspec.args, ["number", "lst"]) - self.assertEqual(props.argspec.defaults[0], []) + self.assertEqual(repr(props.argspec.defaults[0]), "[]") def test_issue_966_static_method(self): class Issue966(Sequence): @@ -252,7 +254,7 @@ def bmethod(number, lst): ) self.assertEqual(props.func, "cmethod") self.assertEqual(props.argspec.args, ["number", "lst"]) - self.assertEqual(props.argspec.defaults[0], []) + self.assertEqual(repr(props.argspec.defaults[0]), "[]") class A: From 6b390db6ffb14a7d57a9d5458a814e41a708d123 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 26 Oct 2022 22:20:38 +0200 Subject: [PATCH 1534/1650] Unbreak tests after 9173825 --- bpython/inspection.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index e1e4ed8db..db0a397c0 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -185,13 +185,7 @@ def _fix_default_values(f: Callable, argspec: ArgSpec) -> ArgSpec: # some situations. See issue #94. return argspec except TypeError: - # No source code is available (for Python >= 3.11) - # - # If the function is a builtin, we replace the default values. - # Otherwise, let's bail out. - if not inspect.isbuiltin(f): - raise - + # No source code is available, so replace the default values with what we have. if argspec.defaults is not None: argspec.defaults = [_Repr(str(value)) for value in argspec.defaults] if argspec.kwonly_defaults is not None: From e20bf119949c445fbb1215f402d67057343476e3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 29 Oct 2022 17:54:09 +0200 Subject: [PATCH 1535/1650] Skip test with special unicode chars on broken Python 3.11 versions --- bpython/test/test_curtsies_painting.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 2804643ce..19561efb9 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -13,7 +13,7 @@ ) from curtsies.fmtfuncs import cyan, bold, green, yellow, on_magenta, red from curtsies.window import CursorAwareWindow -from unittest import mock +from unittest import mock, skipIf from bpython.curtsiesfrontend.events import RefreshRequestEvent from bpython import config, inspection @@ -311,6 +311,10 @@ def test_cursor_position_with_padding_char(self): cursor_pos = self.repl.paint()[1] self.assertEqual(cursor_pos, (1, 4)) + @skipIf( + sys.version_info[:2] >= (3, 11) and sys.version_info[:3] < (3, 11, 1), + "https://github.com/python/cpython/issues/98744", + ) def test_display_of_padding_chars(self): self.repl.width = 11 [self.repl.add_normal_character(c) for c in "width"] From abf13c57a2b54ef4c68e5bec16bcb44a11a9513e Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 29 Oct 2022 18:05:02 +0200 Subject: [PATCH 1536/1650] Remove unused build dependency --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c7ef64f28..4dfbad888 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,6 @@ [build-system] requires = [ "setuptools >= 43", - "wheel", ] [tool.black] From 7f546d932552256ad46057c81f9c8642d7a7b619 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 29 Oct 2022 18:05:11 +0200 Subject: [PATCH 1537/1650] Set build-backend --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 4dfbad888..6526fb9ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,7 @@ requires = [ "setuptools >= 43", ] +build-backend = "setuptools.build_meta" [tool.black] line-length = 80 From 33c62dac03cddd6d87547cdcbca4762b1f4c5684 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 29 Oct 2022 18:18:02 +0200 Subject: [PATCH 1538/1650] Update changelog --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b9a4c8b55..1d9026348 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,10 +12,13 @@ New features: Fixes: +* Improve inspection of builtin functions. Changes to dependencies: +Support for Python 3.11 has been added. + 0.23 ---- From 7712e282d067b041ead41f0b514ac3e27584edc6 Mon Sep 17 00:00:00 2001 From: Thomas Ballinger Date: Thu, 3 Nov 2022 23:51:22 -0700 Subject: [PATCH 1539/1650] Fix watchdog auto-reloading --- bpython/curtsies.py | 2 +- bpython/curtsiesfrontend/repl.py | 36 +++++++------------------------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 6d289aaa6..985f70853 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -106,7 +106,7 @@ def _schedule_refresh(self, when: float) -> None: return self._schedule_refresh_callback(when) def _request_reload(self, files_modified: Sequence[str]) -> None: - return self._request_reload_callback(files_modified) + return self._request_reload_callback(files_modified=files_modified) def interrupting_refresh(self) -> None: return self._interrupting_refresh_callback() diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index b950be68c..cf7d9c37c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -269,54 +269,39 @@ def __init__(self, watcher, loader): def __getattr__(self, name): if name == "create_module" and hasattr(self.loader, name): return self._create_module - if name == "load_module" and hasattr(self.loader, name): - return self._load_module return getattr(self.loader, name) def _create_module(self, spec): - spec = self.loader.create_module(spec) + module_object = self.loader.create_module(spec) if ( getattr(spec, "origin", None) is not None and spec.origin != "builtin" ): self.watcher.track_module(spec.origin) - return spec - - def _load_module(self, name): - module = self.loader.load_module(name) - if hasattr(module, "__file__"): - self.watcher.track_module(module.__file__) - return module + return module_object class ImportFinder: - """Wrapper for finders in sys.meta_path to replace wrap all loaders with ImportLoader.""" + """Wrapper for finders in sys.meta_path to wrap all loaders with ImportLoader.""" - def __init__(self, finder, watcher): + def __init__(self, watcher, finder): self.watcher = watcher self.finder = finder def __getattr__(self, name): if name == "find_spec" and hasattr(self.finder, name): return self._find_spec - if name == "find_module" and hasattr(self.finder, name): - return self._find_module return getattr(self.finder, name) def _find_spec(self, fullname, path, target=None): # Attempt to find the spec spec = self.finder.find_spec(fullname, path, target) if spec is not None: - if getattr(spec, "__loader__", None) is not None: + if getattr(spec, "loader", None) is not None: # Patch the loader to enable reloading - spec.__loader__ = ImportLoader(self.watcher, spec.__loader__) + spec.loader = ImportLoader(self.watcher, spec.loader) return spec - def _find_module(self, fullname, path=None): - loader = self.finder.find_module(fullname, path) - if loader is not None: - return ImportLoader(self.watcher, loader) - def _process_ps(ps, default_ps: str): """Replace ps1/ps2 with the default if the user specified value contains control characters.""" @@ -607,14 +592,7 @@ def __enter__(self): if self.watcher: meta_path = [] for finder in sys.meta_path: - # All elements get wrapped in ImportFinder instances execepted for instances of - # _SixMetaPathImporter (from six). When importing six, it will check if the importer - # is already part of sys.meta_path and will remove instances. We do not want to - # break this feature (see also #874). - if type(finder).__name__ == "_SixMetaPathImporter": - meta_path.append(finder) - else: - meta_path.append(ImportFinder(finder, self.watcher)) + meta_path.append(ImportFinder(self.watcher, finder)) sys.meta_path = meta_path sitefix.monkeypatch_quit() From 7c312971ce37dbeddd4316ed80cb640e50f04a9d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 6 Nov 2022 19:32:40 +0100 Subject: [PATCH 1540/1650] Log versions of all required dependencies --- bpython/args.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bpython/args.py b/bpython/args.py index ec7d3b299..0c514c37b 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -41,6 +41,7 @@ import pygments import requests import sys +import xdg from pathlib import Path from . import __version__, __copyright__ @@ -206,10 +207,12 @@ def callback(group): logger.info("Starting bpython %s", __version__) logger.info("Python %s: %s", sys.executable, sys.version_info) + # versions of required dependencies logger.info("curtsies: %s", curtsies.__version__) logger.info("cwcwidth: %s", cwcwidth.__version__) logger.info("greenlet: %s", greenlet.__version__) logger.info("pygments: %s", pygments.__version__) # type: ignore + logger.info("pyxdg: %s", xdg.__version__) # type: ignore logger.info("requests: %s", requests.__version__) logger.info("environment:") for key, value in sorted(os.environ.items()): From 84314021dd26f254a93bc3f5ac56ac584b8accd3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 6 Nov 2022 19:32:50 +0100 Subject: [PATCH 1541/1650] Log versions of optional dependencies --- bpython/args.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/bpython/args.py b/bpython/args.py index 0c514c37b..2eb910d5b 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -214,6 +214,27 @@ def callback(group): logger.info("pygments: %s", pygments.__version__) # type: ignore logger.info("pyxdg: %s", xdg.__version__) # type: ignore logger.info("requests: %s", requests.__version__) + + # versions of optional dependencies + try: + import pyperclip + + logger.info("pyperclip: %s", pyperclip.__version__) # type: ignore + except ImportError: + logger.info("pyperclip: not available") + try: + import jedi + + logger.info("jedi: %s", jedi.__version__) + except ImportError: + logger.info("jedi: not available") + try: + import watchdog + + logger.info("watchdog: available") + except ImportError: + logger.info("watchdog: not available") + logger.info("environment:") for key, value in sorted(os.environ.items()): if key.startswith("LC") or key.startswith("LANG") or key == "TERM": From b387c5a666d3bec79833754b177fd9f26dfe6886 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 6 Nov 2022 19:40:24 +0100 Subject: [PATCH 1542/1650] Remove no longer used pycheck config skip-checks: true --- .pycheckrc | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .pycheckrc diff --git a/.pycheckrc b/.pycheckrc deleted file mode 100644 index e7050fad1..000000000 --- a/.pycheckrc +++ /dev/null @@ -1 +0,0 @@ -blacklist = ['pyparsing', 'code', 'pygments/lexer'] From f797aa1d5af825e7ef266ded83488c587dcc725d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 3 Dec 2022 21:12:35 +0100 Subject: [PATCH 1543/1650] Fix Python 3.11 version constraints --- bpython/test/test_interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index f53ec2529..b2fb52758 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -112,7 +112,7 @@ def gfunc(): global_not_found = "name 'gfunc' is not defined" - if (3, 11) <= sys.version_info[:2]: + if (3, 11, 0) <= sys.version_info[:3] < (3, 11, 1): expected = ( "Traceback (most recent call last):\n File " + green('""') From 352de3085e2f320000d8cd8d49378c4b14d22ec3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 3 Dec 2022 21:22:12 +0100 Subject: [PATCH 1544/1650] Fix type annotations --- bpython/cli.py | 10 ++++++---- bpython/translations/__init__.py | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bpython/cli.py b/bpython/cli.py index 886dc2c85..bb1429a8e 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -1299,7 +1299,7 @@ def show_list( arg_pos: Union[str, int, None], topline: Optional[inspection.FuncProps] = None, formatter: Optional[Callable] = None, - current_item: Union[str, Literal[False]] = None, + current_item: Optional[str] = None, ) -> None: v_items: Collection shared = ShowListState() @@ -1315,7 +1315,7 @@ def show_list( if items and formatter: items = [formatter(x) for x in items] - if current_item: + if current_item is not None: current_item = formatter(current_item) if topline: @@ -1492,8 +1492,10 @@ def tab(self, back: bool = False) -> bool: # 4. swap current word for a match list item elif self.matches_iter.matches: - current_match: Union[str, Literal[False]] = ( - back and self.matches_iter.previous() or next(self.matches_iter) + current_match = ( + self.matches_iter.previous() + if back + else next(self.matches_iter) ) try: f = None diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index 13c498025..0cb4c01f6 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -2,7 +2,7 @@ import locale import os.path import sys -from typing import cast, List +from typing import Optional, cast, List from .. import package_dir @@ -17,7 +17,9 @@ def ngettext(singular, plural, n): return translator.ngettext(singular, plural, n) -def init(locale_dir: str = None, languages: List[str] = None) -> None: +def init( + locale_dir: Optional[str] = None, languages: Optional[List[str]] = None +) -> None: try: locale.setlocale(locale.LC_ALL, "") except locale.Error: From ebefde843bce5ec1e3f180c655308d5858672531 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 17 Dec 2022 21:24:18 +0100 Subject: [PATCH 1545/1650] Revert "Fix Python 3.11 version constraints" This reverts commit f797aa1d5af825e7ef266ded83488c587dcc725d. --- bpython/test/test_interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index b2fb52758..f53ec2529 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -112,7 +112,7 @@ def gfunc(): global_not_found = "name 'gfunc' is not defined" - if (3, 11, 0) <= sys.version_info[:3] < (3, 11, 1): + if (3, 11) <= sys.version_info[:2]: expected = ( "Traceback (most recent call last):\n File " + green('""') From 71320bbfa318ec7aebd7bfafe3652715087fe7d2 Mon Sep 17 00:00:00 2001 From: Eric Burgess Date: Fri, 13 Jan 2023 13:06:13 -0600 Subject: [PATCH 1546/1650] Add more keywords to trigger auto-deindent --- bpython/curtsiesfrontend/repl.py | 4 +++- bpython/repl.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index cf7d9c37c..037cf7567 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1252,7 +1252,9 @@ def predicted_indent(self, line): elif ( line and ":" not in line - and line.strip().startswith(("return", "pass", "raise", "yield")) + and line.strip().startswith( + ("return", "pass", "...", "raise", "yield", "break", "continue") + ) ): indent = max(0, indent - self.config.tab_length) logger.debug("indent we found was %s", indent) diff --git a/bpython/repl.py b/bpython/repl.py index 8aea4e172..c964f0902 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1255,7 +1255,9 @@ def next_indentation(line, tab_length) -> int: if line.rstrip().endswith(":"): indentation += 1 elif indentation >= 1: - if line.lstrip().startswith(("return", "pass", "raise", "yield")): + if line.lstrip().startswith( + ("return", "pass", "...", "raise", "yield", "break", "continue") + ): indentation -= 1 return indentation From d0da704b96b37ce4611e91d59c09f922e3908246 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 14 Jan 2023 22:10:12 +0100 Subject: [PATCH 1547/1650] Update changelog for 0.24 --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1d9026348..4f94198d2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,9 +6,12 @@ Changelog General information: +* This release is focused on Python 3.11 support. New features: +* #980: Add more keywords to trigger auto-deindent. + Thanks to Eric Burgess Fixes: @@ -16,6 +19,7 @@ Fixes: Changes to dependencies: +* wheel is no required as part of pyproject.toml's build dependencies Support for Python 3.11 has been added. From b94ccc24cf94bb6e29bd2208f462fcc9b7c49cb2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 14 Jan 2023 22:18:10 +0100 Subject: [PATCH 1548/1650] Update readthedocs config again --- .readthedocs.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 942c1da0b..a19293daa 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,5 +1,13 @@ version: 2 +build: + os: ubuntu-22.04 + tools: + python: "3" + +sphinx: + configuration: doc/sphinx/source/conf.py + python: install: - method: pip From d6e62b372504c290fa96825535317acf067463f6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 14 Jan 2023 22:27:13 +0100 Subject: [PATCH 1549/1650] Avoid bpython imports in setup.py --- setup.cfg | 2 ++ setup.py | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/setup.cfg b/setup.cfg index 7955ee390..07f90115a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,6 +3,8 @@ name = bpython long_description = file: README.rst license = MIT license_files = LICENSE +author = Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al. +author_email = bpython@googlegroups.com url = https://www.bpython-interpreter.org/ project_urls = GitHub = https://github.com/bpython/bpython diff --git a/setup.py b/setup.py index 12d4eeec5..6790b9d78 100755 --- a/setup.py +++ b/setup.py @@ -124,9 +124,7 @@ def git_describe_to_python_version(version): cmdclass = {"build": build} -from bpython import package_dir, __author__ - -translations_dir = os.path.join(package_dir, "translations") +translations_dir = os.path.join("bpython", "translations") # localization options if using_translations: @@ -179,8 +177,6 @@ def git_describe_to_python_version(version): setup( version=version, - author=__author__, - author_email="robertanthonyfarrell@gmail.com", data_files=data_files, package_data={ "bpython": ["sample-config"], From 406f8713915566a77c7021daf160b8a8cc9ee863 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 14 Jan 2023 22:27:23 +0100 Subject: [PATCH 1550/1650] Update copyright year --- bpython/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/__init__.py b/bpython/__init__.py index adc00c06b..dff06c0fa 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -30,7 +30,7 @@ __author__ = ( "Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al." ) -__copyright__ = f"(C) 2008-2020 {__author__}" +__copyright__ = f"(C) 2008-2023 {__author__}" __license__ = "MIT" __version__ = version package_dir = os.path.abspath(os.path.dirname(__file__)) From b38f6c7a483302651b223e13a0c478224bc6d645 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 17 Dec 2022 21:44:27 +0100 Subject: [PATCH 1551/1650] Remove unused code --- bpython/test/test_interpreter.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index f53ec2529..4a39cfe14 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -1,37 +1,25 @@ import sys -import re import unittest from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain -from unittest import mock from bpython.curtsiesfrontend import interpreter pypy = "PyPy" in sys.version -def remove_ansi(s): - return re.sub(r"(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]".encode("ascii"), b"", s) +class Interpreter(interpreter.Interp): + def __init__(self): + super().__init__() + self.a = [] + def write(self, data): + self.a.append(data) -class TestInterpreter(unittest.TestCase): - def interp_errlog(self): - i = interpreter.Interp() - a = [] - i.write = a.append - return i, a - - def err_lineno(self, a): - strings = [x.__unicode__() for x in a] - for line in reversed(strings): - clean_line = remove_ansi(line) - m = re.search(r"line (\d+)[,]", clean_line) - if m: - return int(m.group(1)) - return None +class TestInterpreter(unittest.TestCase): def test_syntaxerror(self): - i, a = self.interp_errlog() + i = Interpreter() i.runsource("1.1.1.1") @@ -96,11 +84,12 @@ def test_syntaxerror(self): + "\n" ) + a = i.a self.assertMultiLineEqual(str(plain("").join(a)), str(expected)) self.assertEqual(plain("").join(a), expected) def test_traceback(self): - i, a = self.interp_errlog() + i = Interpreter() def f(): return 1 / 0 @@ -142,6 +131,7 @@ def gfunc(): + "\n" ) + a = i.a self.assertMultiLineEqual(str(plain("").join(a)), str(expected)) self.assertEqual(plain("").join(a), expected) From e1ca4522583f06b172674c4e11303fda044bf8f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 Jan 2023 17:25:59 +0100 Subject: [PATCH 1552/1650] Start development of 0.25 --- CHANGELOG.rst | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4f94198d2..7ce32c42c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,21 @@ Changelog ========= +0.25 +---- + +General information: + + +New features: + + +Fixes: + + +Changes to dependencies: + + 0.24 ---- @@ -23,7 +38,6 @@ Changes to dependencies: Support for Python 3.11 has been added. - 0.23 ---- From c1f0385c79c0096265994b0d6e0aadf6db8a2709 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 Jan 2023 17:34:55 +0100 Subject: [PATCH 1553/1650] Do not fail if curtsies is not available (fixes #978) Also delay imports of dependencies only used to log the version. --- bpython/args.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 2eb910d5b..b9e68e1d8 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -30,19 +30,13 @@ """ import argparse -from typing import Tuple, List, Optional, NoReturn, Callable import code -import curtsies -import cwcwidth -import greenlet import importlib.util import logging import os -import pygments -import requests import sys -import xdg from pathlib import Path +from typing import Tuple, List, Optional, NoReturn, Callable from . import __version__, __copyright__ from .config import default_config_path, Config @@ -205,10 +199,22 @@ def callback(group): bpython_logger.addHandler(logging.NullHandler()) curtsies_logger.addHandler(logging.NullHandler()) + import cwcwidth + import greenlet + import pygments + import requests + import xdg + logger.info("Starting bpython %s", __version__) logger.info("Python %s: %s", sys.executable, sys.version_info) # versions of required dependencies - logger.info("curtsies: %s", curtsies.__version__) + try: + import curtsies + + logger.info("curtsies: %s", curtsies.__version__) + except ImportError: + # may happen on Windows + logger.info("curtsies: not available") logger.info("cwcwidth: %s", cwcwidth.__version__) logger.info("greenlet: %s", greenlet.__version__) logger.info("pygments: %s", pygments.__version__) # type: ignore From 29d6f87f9688610a1f96cdcc32ce84f3ce435b7f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 Jan 2023 17:52:17 +0100 Subject: [PATCH 1554/1650] Fix definition of write --- bpython/test/test_interpreter.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 4a39cfe14..a4a32dd09 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -12,9 +12,7 @@ class Interpreter(interpreter.Interp): def __init__(self): super().__init__() self.a = [] - - def write(self, data): - self.a.append(data) + self.write = self.a.append class TestInterpreter(unittest.TestCase): From 46dc081faa21aa957f67761a15d0fb18031de87c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 Jan 2023 18:00:39 +0100 Subject: [PATCH 1555/1650] Remove support for Python 3.7 (fixes #940) --- .github/workflows/build.yaml | 5 ++-- bpython/_typing_compat.py | 33 --------------------------- bpython/cli.py | 2 +- bpython/curtsies.py | 2 +- bpython/curtsiesfrontend/_internal.py | 3 +-- bpython/curtsiesfrontend/repl.py | 2 +- bpython/filelock.py | 3 +-- bpython/inspection.py | 12 ++++++++-- bpython/paste.py | 3 +-- bpython/repl.py | 14 ++++++------ doc/sphinx/source/contributing.rst | 2 +- doc/sphinx/source/releases.rst | 2 +- pyproject.toml | 2 +- requirements.txt | 1 - setup.cfg | 4 +--- 15 files changed, 29 insertions(+), 61 deletions(-) delete mode 100644 bpython/_typing_compat.py diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9cdcc1e92..1e2a374a5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,17 +10,16 @@ on: jobs: build: runs-on: ubuntu-latest - continue-on-error: ${{ matrix.python-version == 'pypy-3.7' }} + continue-on-error: ${{ matrix.python-version == 'pypy-3.8' }} strategy: fail-fast: false matrix: python-version: - - "3.7" - "3.8" - "3.9" - "3.10" - "3.11" - - "pypy-3.7" + - "pypy-3.8" steps: - uses: actions/checkout@v3 with: diff --git a/bpython/_typing_compat.py b/bpython/_typing_compat.py deleted file mode 100644 index 31fb64287..000000000 --- a/bpython/_typing_compat.py +++ /dev/null @@ -1,33 +0,0 @@ -# The MIT License -# -# Copyright (c) 2021 Sebastian Ramacher -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -try: - # introduced in Python 3.8 - from typing import Literal -except ImportError: - from typing_extensions import Literal # type: ignore - -try: - # introduced in Python 3.8 - from typing import Protocol -except ImportError: - from typing_extensions import Protocol # type: ignore diff --git a/bpython/cli.py b/bpython/cli.py index bb1429a8e..0a03d5138 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -69,12 +69,12 @@ Collection, Dict, TYPE_CHECKING, + Literal, ) if TYPE_CHECKING: from _curses import _CursesWindow -from ._typing_compat import Literal import unicodedata from dataclasses import dataclass diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 985f70853..6dc8d1f78 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -28,11 +28,11 @@ Generator, List, Optional, + Protocol, Sequence, Tuple, Union, ) -from ._typing_compat import Protocol logger = logging.getLogger(__name__) diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 79c5e974c..0480c1b06 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -22,8 +22,7 @@ import pydoc from types import TracebackType -from typing import Optional, Type -from .._typing_compat import Literal +from typing import Optional, Type, Literal from .. import _internal diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 037cf7567..a3b32d443 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -17,13 +17,13 @@ Iterable, Dict, List, + Literal, Optional, Sequence, Tuple, Type, Union, ) -from .._typing_compat import Literal import greenlet from curtsies import ( diff --git a/bpython/filelock.py b/bpython/filelock.py index 429f708b6..11f575b6e 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -20,8 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from typing import Optional, Type, IO -from ._typing_compat import Literal +from typing import Optional, Type, IO, Literal from types import TracebackType has_fcntl = True diff --git a/bpython/inspection.py b/bpython/inspection.py index db0a397c0..fe1e3a0a2 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -26,9 +26,17 @@ import pydoc import re from dataclasses import dataclass -from typing import Any, Callable, Optional, Type, Dict, List, ContextManager +from typing import ( + Any, + Callable, + Optional, + Type, + Dict, + List, + ContextManager, + Literal, +) from types import MemberDescriptorType, TracebackType -from ._typing_compat import Literal from pygments.token import Token from pygments.lexers import Python3Lexer diff --git a/bpython/paste.py b/bpython/paste.py index ceba59386..fd140a0ec 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -22,7 +22,7 @@ import errno import subprocess -from typing import Optional, Tuple +from typing import Optional, Tuple, Protocol from urllib.parse import urljoin, urlparse import requests @@ -30,7 +30,6 @@ from .config import getpreferredencoding from .translations import _ -from ._typing_compat import Protocol class PasteFailed(Exception): diff --git a/bpython/repl.py b/bpython/repl.py index c964f0902..fe72a4db8 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -40,19 +40,19 @@ from pathlib import Path from types import ModuleType, TracebackType from typing import ( + Any, + Callable, + Dict, Iterable, - cast, List, - Tuple, - Any, + Literal, Optional, + TYPE_CHECKING, + Tuple, Type, Union, - Callable, - Dict, - TYPE_CHECKING, + cast, ) -from ._typing_compat import Literal from pygments.lexers import Python3Lexer from pygments.token import Token, _TokenType diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 54fd56c6b..9e0f6bc44 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -17,7 +17,7 @@ the time of day. Getting your development environment set up ------------------------------------------- -bpython supports Python 3.7 and newer. The code is compatible with all +bpython supports Python 3.8 and newer. The code is compatible with all supported versions. Using a virtual environment is probably a good idea. Create a virtual diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst index 738c24ff2..fcce5c1cf 100644 --- a/doc/sphinx/source/releases.rst +++ b/doc/sphinx/source/releases.rst @@ -45,7 +45,7 @@ A checklist to perform some manual tests before a release: Check that all of the following work before a release: -* Runs under Python 3.7 - 3.9 +* Runs under Python 3.8 - 3.11 * Save * Rewind * Pastebin diff --git a/pyproject.toml b/pyproject.toml index 6526fb9ed..b7bd3196a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 80 -target_version = ["py37"] +target_version = ["py38"] include = '\.pyi?$' exclude = ''' /( diff --git a/requirements.txt b/requirements.txt index ba8b126d1..4c750a694 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ Pygments -backports.cached-property; python_version < "3.8" curtsies >=0.4.0 cwcwidth greenlet diff --git a/setup.cfg b/setup.cfg index 07f90115a..081af0bc2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,7 +13,7 @@ classifiers = Programming Language :: Python :: 3 [options] -python_requires = >=3.7 +python_requires = >=3.8 packages = bpython bpython.curtsiesfrontend @@ -22,14 +22,12 @@ packages = bpython.translations bpdb install_requires = - backports.cached-property; python_version < "3.8" curtsies >=0.4.0 cwcwidth greenlet pygments pyxdg requests - typing-extensions; python_version < "3.8" [options.extras_require] clipboard = pyperclip From 274f423e4a1b742904a87d5cebe18258ecd095a9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 Jan 2023 18:13:26 +0100 Subject: [PATCH 1556/1650] Remove < 3.8 workaround --- bpython/importcompletion.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index c1e073f8e..9b0edaab6 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -175,17 +175,8 @@ def find_modules(self, path: Path) -> Generator[Optional[str], None, None]: # Path is on skiplist return - try: - # https://bugs.python.org/issue34541 - # Once we migrate to Python 3.8, we can change it back to directly iterator over - # path.iterdir(). - children = tuple(path.iterdir()) - except OSError: - # Path is not readable - return - finder = importlib.machinery.FileFinder(str(path), *LOADERS) # type: ignore - for p in children: + for p in path.iterdir(): if p.name.startswith(".") or p.name == "__pycache__": # Impossible to import from names starting with . and we can skip __pycache__ continue From 1fb4041aa641d9b06047489c487cafbeb7d3d7b2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 Jan 2023 19:06:34 +0100 Subject: [PATCH 1557/1650] Handle OSError again --- bpython/importcompletion.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 9b0edaab6..7833a9328 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -175,8 +175,14 @@ def find_modules(self, path: Path) -> Generator[Optional[str], None, None]: # Path is on skiplist return + try: + children = path.iterdir() + except OSError: + # Path is not readable + return + finder = importlib.machinery.FileFinder(str(path), *LOADERS) # type: ignore - for p in path.iterdir(): + for p in children: if p.name.startswith(".") or p.name == "__pycache__": # Impossible to import from names starting with . and we can skip __pycache__ continue From df3750d2a93ce69d9676f6b17a0da858df7ca983 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 15 Jan 2023 19:43:34 +0100 Subject: [PATCH 1558/1650] Really handle OSError --- bpython/importcompletion.py | 108 ++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 7833a9328..9df140c64 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -175,65 +175,67 @@ def find_modules(self, path: Path) -> Generator[Optional[str], None, None]: # Path is on skiplist return - try: - children = path.iterdir() - except OSError: - # Path is not readable - return - finder = importlib.machinery.FileFinder(str(path), *LOADERS) # type: ignore - for p in children: - if p.name.startswith(".") or p.name == "__pycache__": - # Impossible to import from names starting with . and we can skip __pycache__ - continue - elif any(fnmatch.fnmatch(p.name, entry) for entry in self.skiplist): - # Path is on skiplist - continue - elif not any(p.name.endswith(suffix) for suffix in SUFFIXES): - # Possibly a package - if "." in p.name: + try: + for p in path.iterdir(): + if p.name.startswith(".") or p.name == "__pycache__": + # Impossible to import from names starting with . and we can skip __pycache__ continue - elif p.is_dir(): - # Unfortunately, CPython just crashes if there is a directory - # which ends with a python extension, so work around. - continue - name = p.name - for suffix in SUFFIXES: - if name.endswith(suffix): - name = name[: -len(suffix)] - break - if name == "badsyntax_pep3120": - # Workaround for issue #166 - continue - - package_pathname = None - try: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", ImportWarning) - spec = finder.find_spec(name) - if spec is None: + elif any( + fnmatch.fnmatch(p.name, entry) for entry in self.skiplist + ): + # Path is on skiplist + continue + elif not any(p.name.endswith(suffix) for suffix in SUFFIXES): + # Possibly a package + if "." in p.name: continue - if spec.submodule_search_locations is not None: - package_pathname = spec.submodule_search_locations[0] - except (ImportError, OSError, SyntaxError, UnicodeEncodeError): - # UnicodeEncodeError happens with Python 3 when there is a filename in some invalid encoding - continue + elif p.is_dir(): + # Unfortunately, CPython just crashes if there is a directory + # which ends with a python extension, so work around. + continue + name = p.name + for suffix in SUFFIXES: + if name.endswith(suffix): + name = name[: -len(suffix)] + break + if name == "badsyntax_pep3120": + # Workaround for issue #166 + continue - if package_pathname is not None: - path_real = Path(package_pathname).resolve() + package_pathname = None try: - stat = path_real.stat() - except OSError: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + spec = finder.find_spec(name) + if spec is None: + continue + if spec.submodule_search_locations is not None: + package_pathname = spec.submodule_search_locations[ + 0 + ] + except (ImportError, OSError, SyntaxError, UnicodeEncodeError): + # UnicodeEncodeError happens with Python 3 when there is a filename in some invalid encoding continue - loaded_inode = _LoadedInode(stat.st_dev, stat.st_ino) - if loaded_inode not in self.paths: - self.paths.add(loaded_inode) - for subname in self.find_modules(path_real): - if subname is None: - yield None # take a break to avoid unresponsiveness - elif subname != "__init__": - yield f"{name}.{subname}" - yield name + + if package_pathname is not None: + path_real = Path(package_pathname).resolve() + try: + stat = path_real.stat() + except OSError: + continue + loaded_inode = _LoadedInode(stat.st_dev, stat.st_ino) + if loaded_inode not in self.paths: + self.paths.add(loaded_inode) + for subname in self.find_modules(path_real): + if subname is None: + yield None # take a break to avoid unresponsiveness + elif subname != "__init__": + yield f"{name}.{subname}" + yield name + except OSError: + # Path is not readable + return yield None # take a break to avoid unresponsiveness def find_all_modules( From b537a508ede7b09eb118f20898bd3c8c0a2fe7f9 Mon Sep 17 00:00:00 2001 From: Ganden Schaffner Date: Sun, 29 Jan 2023 00:00:00 -0800 Subject: [PATCH 1559/1650] Fix URL typo in package metadata --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 081af0bc2..8c4294d9c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,7 +8,7 @@ author_email = bpython@googlegroups.com url = https://www.bpython-interpreter.org/ project_urls = GitHub = https://github.com/bpython/bpython - Documentation = https://doc.bpython-interpreter.org + Documentation = https://docs.bpython-interpreter.org classifiers = Programming Language :: Python :: 3 From 11a72fb11d48f8acd018acd24a538448ff57c2c2 Mon Sep 17 00:00:00 2001 From: Nitant Patel Date: Thu, 2 Mar 2023 00:48:19 -0500 Subject: [PATCH 1560/1650] Clarify fork instructions in contributing docs `setup.py` expects to be able to find git tags in order to properly set the package version. If a fork does not have any tags, `pip install -e .` will fail due to the default `"unknown"` being an invalid version. --- doc/sphinx/source/contributing.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 9e0f6bc44..32b1ea869 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -30,7 +30,10 @@ environment with # necessary every time you work on bpython $ source bpython-dev/bin/activate -Fork bpython in the GitHub web interface, then clone the repo: +Fork bpython in the GitHub web interface. Be sure to include the tags +in your fork by un-selecting the option to copy only the main branch. + +Then, clone the forked repo: .. code-block:: bash From 7df8e6f89036775ad659983d45deda98a175d2be Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 2 Mar 2023 09:53:04 +0100 Subject: [PATCH 1561/1650] Apply black --- bpython/autocomplete.py | 2 +- bpython/cli.py | 1 + bpython/config.py | 2 +- bpython/curtsiesfrontend/manual_readline.py | 1 - bpython/curtsiesfrontend/repl.py | 1 - bpython/repl.py | 10 ++++------ bpython/test/__init__.py | 1 - bpython/test/test_repl.py | 4 ++-- bpython/urwid.py | 2 -- 9 files changed, 9 insertions(+), 15 deletions(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index b97fd86f7..e0849c6d2 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -57,6 +57,7 @@ logger = logging.getLogger(__name__) + # Autocomplete modes class AutocompleteModes(Enum): NONE = "none" @@ -381,7 +382,6 @@ def format(self, filename: str) -> str: class AttrCompletion(BaseCompletionType): - attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)") def matches( diff --git a/bpython/cli.py b/bpython/cli.py index 0a03d5138..fcfd11fa5 100644 --- a/bpython/cli.py +++ b/bpython/cli.py @@ -264,6 +264,7 @@ def readlines(self, size: int = -1) -> List[str]: # the addstr stuff to a higher level. # + # Have to ignore the return type on this one because the colors variable # is Optional[MutableMapping[str, int]] but for the purposes of this # function it can't be None diff --git a/bpython/config.py b/bpython/config.py index 29b906ddb..5123ec226 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -90,7 +90,7 @@ def fill_config_with_default_values( if not config.has_section(section): config.add_section(section) - for (opt, val) in default_values[section].items(): + for opt, val in default_values[section].items(): if not config.has_option(section, opt): config.set(section, opt, str(val)) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index f95e66c59..206e5278b 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -16,7 +16,6 @@ class AbstractEdits: - default_kwargs = { "line": "hello world", "cursor_offset": 5, diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index a3b32d443..e4819e193 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -893,7 +893,6 @@ def insert_char_pair_end(self, e): self.add_normal_character(e) def get_last_word(self): - previous_word = _last_word(self.rl_history.entry) word = _last_word(self.rl_history.back()) line = self.current_line diff --git a/bpython/repl.py b/bpython/repl.py index fe72a4db8..f30cfa31d 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -465,7 +465,6 @@ def cursor_offset(self, value: int) -> None: self._set_cursor_offset(value) if TYPE_CHECKING: - # not actually defined, subclasses must define cpos: int @@ -567,7 +566,7 @@ def current_string(self, concatenate=False): return "" opening = string_tokens.pop()[1] string = list() - for (token, value) in reversed(string_tokens): + for token, value in reversed(string_tokens): if token is Token.Text: continue elif opening is None: @@ -602,7 +601,7 @@ def _funcname_and_argnum( # if keyword is not None, we've encountered a keyword and so we're done counting stack = [_FuncExpr("", "", 0, "")] try: - for (token, value) in Python3Lexer().get_tokens(line): + for token, value in Python3Lexer().get_tokens(line): if token is Token.Punctuation: if value in "([{": stack.append(_FuncExpr("", "", 0, value)) @@ -692,7 +691,6 @@ def get_args(self): # py3 f.__new__.__class__ is not object.__new__.__class__ ): - class_f = f.__new__ if class_f: @@ -1117,7 +1115,7 @@ def tokenize(self, s, newline=False) -> List[Tuple[_TokenType, str]]: line_tokens: List[Tuple[_TokenType, str]] = list() saved_tokens: List[Tuple[_TokenType, str]] = list() search_for_paren = True - for (token, value) in split_lines(all_tokens): + for token, value in split_lines(all_tokens): pos += len(value) if token is Token.Text and value == "\n": line += 1 @@ -1263,7 +1261,7 @@ def next_indentation(line, tab_length) -> int: def split_lines(tokens): - for (token, value) in tokens: + for token, value in tokens: if not value: continue while value: diff --git a/bpython/test/__init__.py b/bpython/test/__init__.py index 7722278cc..4618eca4d 100644 --- a/bpython/test/__init__.py +++ b/bpython/test/__init__.py @@ -13,7 +13,6 @@ def setUpClass(cls): class MagicIterMock(unittest.mock.MagicMock): - __next__ = unittest.mock.Mock(return_value=None) diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 63309364c..b3e8912fd 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -183,7 +183,7 @@ def set_input_line(self, line): self.repl.cursor_offset = len(line) def test_func_name(self): - for (line, expected_name) in [ + for line, expected_name in [ ("spam(", "spam"), # map pydoc has no signature in pypy ("spam(any([]", "any") if pypy else ("spam(map([]", "map"), @@ -194,7 +194,7 @@ def test_func_name(self): self.assertEqual(self.repl.current_func.__name__, expected_name) def test_func_name_method_issue_479(self): - for (line, expected_name) in [ + for line, expected_name in [ ("o.spam(", "spam"), # map pydoc has no signature in pypy ("o.spam(any([]", "any") if pypy else ("o.spam(map([]", "map"), diff --git a/bpython/urwid.py b/bpython/urwid.py index 66054097e..4b41c12aa 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -74,7 +74,6 @@ else: class EvalProtocol(basic.LineOnlyReceiver): - delimiter = "\n" def __init__(self, myrepl): @@ -570,7 +569,6 @@ def file_prompt(self, s: str) -> Optional[str]: class URWIDRepl(repl.Repl): - _time_between_redraws = 0.05 # seconds def __init__(self, event_loop, palette, interpreter, config): From 44c3b702037c1c1a5f7681b7f86c0246fbf0db7e Mon Sep 17 00:00:00 2001 From: supakeen Date: Tue, 9 Nov 2021 16:51:56 +0000 Subject: [PATCH 1562/1650] Remove deprecated curses (cli) rendering backend. --- CHANGELOG.rst | 4 +- bpython/cli.py | 2094 ----------------- bpython/test/test_repl.py | 152 +- bpython/translations/bpython.pot | 30 +- .../translations/de/LC_MESSAGES/bpython.po | 34 +- .../translations/es_ES/LC_MESSAGES/bpython.po | 31 +- .../translations/fr_FR/LC_MESSAGES/bpython.po | 31 +- .../translations/it_IT/LC_MESSAGES/bpython.po | 31 +- .../translations/nl_NL/LC_MESSAGES/bpython.po | 31 +- doc/sphinx/source/tips.rst | 2 +- doc/sphinx/source/windows.rst | 6 - setup.cfg | 1 - 12 files changed, 16 insertions(+), 2431 deletions(-) delete mode 100644 bpython/cli.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7ce32c42c..d7ecb3ab7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,9 @@ Changelog General information: +* The bpython-cli rendering backend has been removed following deprecation in + version 0.19. + New features: @@ -44,7 +47,6 @@ Support for Python 3.11 has been added. General information: * More and more type annotations have been added to the bpython code base. -* Some work has been performed to stop relying on blessings. New features: diff --git a/bpython/cli.py b/bpython/cli.py deleted file mode 100644 index fcfd11fa5..000000000 --- a/bpython/cli.py +++ /dev/null @@ -1,2094 +0,0 @@ -# The MIT License -# -# Copyright (c) 2008 Bob Farrell -# Copyright (c) bpython authors -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# mypy: disallow_untyped_defs=True -# mypy: disallow_untyped_calls=True - -# Modified by Brandon Navra -# Notes for Windows -# Prerequisites -# - Curses -# - pyreadline -# -# Added -# -# - Support for running on windows command prompt -# - input from numpad keys -# -# Issues -# -# - Suspend doesn't work nor does detection of resizing of screen -# - Instead the suspend key exits the program -# - View source doesn't work on windows unless you install the less program (From GnuUtils or Cygwin) - - -import curses -import errno -import functools -import math -import os -import platform -import re -import struct -import sys -import time -from typing import ( - Iterator, - NoReturn, - List, - MutableMapping, - Any, - Callable, - TypeVar, - cast, - IO, - Iterable, - Optional, - Union, - Tuple, - Collection, - Dict, - TYPE_CHECKING, - Literal, -) - -if TYPE_CHECKING: - from _curses import _CursesWindow - -import unicodedata -from dataclasses import dataclass - -if platform.system() != "Windows": - import signal # Windows does not have job control - import termios # Windows uses curses - import fcntl # Windows uses curses - - -# These are used for syntax highlighting -from pygments import format -from pygments.formatters import TerminalFormatter -from pygments.lexers import Python3Lexer -from pygments.token import Token, _TokenType -from .formatter import BPythonFormatter - -# This for config -from .config import getpreferredencoding, Config - -# This for keys -from .keys import cli_key_dispatch as key_dispatch - -# This for i18n -from . import translations -from .translations import _ - -from . import repl, inspection -from . import args as bpargs -from .pager import page -from .args import parse as argsparse - -F = TypeVar("F", bound=Callable[..., Any]) - -# --- module globals --- -stdscr = None -colors: Optional[MutableMapping[str, int]] = None - -DO_RESIZE = False -# --- - - -@dataclass -class ShowListState: - cols: int = 0 - rows: int = 0 - wl: int = 0 - - -def forward_if_not_current(func: F) -> F: - @functools.wraps(func) - def newfunc(self, *args, **kwargs): # type: ignore - dest = self.get_dest() - if self is dest: - return func(self, *args, **kwargs) - else: - return getattr(self.get_dest(), newfunc.__name__)(*args, **kwargs) - - return cast(F, newfunc) - - -class FakeStream: - """Provide a fake file object which calls functions on the interface - provided.""" - - def __init__(self, interface: "CLIRepl", get_dest: IO[str]) -> None: - self.encoding: str = getpreferredencoding() - self.interface = interface - self.get_dest = get_dest - - @forward_if_not_current - def write(self, s: str) -> None: - self.interface.write(s) - - @forward_if_not_current - def writelines(self, l: Iterable[str]) -> None: - for s in l: - self.write(s) - - def isatty(self) -> bool: - # some third party (amongst them mercurial) depend on this - return True - - def flush(self) -> None: - self.interface.flush() - - -class FakeStdin: - """Provide a fake stdin type for things like raw_input() etc.""" - - def __init__(self, interface: "CLIRepl") -> None: - """Take the curses Repl on init and assume it provides a get_key method - which, fortunately, it does.""" - - self.encoding = getpreferredencoding() - self.interface = interface - self.buffer: List[str] = list() - - def __iter__(self) -> Iterator: - return iter(self.readlines()) - - def flush(self) -> None: - """Flush the internal buffer. This is a no-op. Flushing stdin - doesn't make any sense anyway.""" - - def write(self, value: str) -> NoReturn: - # XXX IPython expects sys.stdin.write to exist, there will no doubt be - # others, so here's a hack to keep them happy - raise OSError(errno.EBADF, "sys.stdin is read-only") - - def isatty(self) -> bool: - return True - - def readline(self, size: int = -1) -> str: - """I can't think of any reason why anything other than readline would - be useful in the context of an interactive interpreter so this is the - only one I've done anything with. The others are just there in case - someone does something weird to stop it from blowing up.""" - - if not size: - return "" - elif self.buffer: - buffer = self.buffer.pop(0) - else: - buffer = "" - - curses.raw(True) - try: - while not buffer.endswith(("\n", "\r")): - key = self.interface.get_key() - if key in (curses.erasechar(), "KEY_BACKSPACE"): - y, x = self.interface.scr.getyx() - if buffer: - self.interface.scr.delch(y, x - 1) - buffer = buffer[:-1] - continue - elif key == chr(4) and not buffer: - # C-d - return "" - elif key not in ("\n", "\r") and ( - len(key) > 1 or unicodedata.category(key) == "Cc" - ): - continue - sys.stdout.write(key) - # Include the \n in the buffer - raw_input() seems to deal with trailing - # linebreaks and will break if it gets an empty string. - buffer += key - finally: - curses.raw(False) - - if size > 0: - rest = buffer[size:] - if rest: - self.buffer.append(rest) - buffer = buffer[:size] - - return buffer - - def read(self, size: Optional[int] = None) -> str: - if size == 0: - return "" - - data = list() - while size is None or size > 0: - line = self.readline(size or -1) - if not line: - break - if size is not None: - size -= len(line) - data.append(line) - - return "".join(data) - - def readlines(self, size: int = -1) -> List[str]: - return list(iter(self.readline, "")) - - -# TODO: -# -# Tab completion does not work if not at the end of the line. -# -# Numerous optimisations can be made but it seems to do all the lookup stuff -# fast enough on even my crappy server so I'm not too bothered about that -# at the moment. -# -# The popup window that displays the argspecs and completion suggestions -# needs to be an instance of a ListWin class or something so I can wrap -# the addstr stuff to a higher level. -# - - -# Have to ignore the return type on this one because the colors variable -# is Optional[MutableMapping[str, int]] but for the purposes of this -# function it can't be None -def get_color(config: Config, name: str) -> int: # type: ignore[return] - global colors - if colors: - return colors[config.color_scheme[name].lower()] - - -def get_colpair(config: Config, name: str) -> int: - return curses.color_pair(get_color(config, name) + 1) - - -def make_colors(config: Config) -> Dict[str, int]: - """Init all the colours in curses and bang them into a dictionary""" - - # blacK, Red, Green, Yellow, Blue, Magenta, Cyan, White, Default: - c = { - "k": 0, - "r": 1, - "g": 2, - "y": 3, - "b": 4, - "m": 5, - "c": 6, - "w": 7, - "d": -1, - } - - if platform.system() == "Windows": - c = dict( - list(c.items()) - + [ - ("K", 8), - ("R", 9), - ("G", 10), - ("Y", 11), - ("B", 12), - ("M", 13), - ("C", 14), - ("W", 15), - ] - ) - - for i in range(63): - if i > 7: - j = i // 8 - else: - j = c[config.color_scheme["background"]] - curses.init_pair(i + 1, i % 8, j) - - return c - - -class CLIInteraction(repl.Interaction): - def __init__(self, config: Config, statusbar: "Statusbar"): - super().__init__(config) - self.statusbar = statusbar - - def confirm(self, q: str) -> bool: - """Ask for yes or no and return boolean""" - try: - reply = self.statusbar.prompt(q) - except ValueError: - return False - - return reply.lower() in (_("y"), _("yes")) - - def notify( - self, s: str, n: float = 10.0, wait_for_keypress: bool = False - ) -> None: - self.statusbar.message(s, n) - - def file_prompt(self, s: str) -> Optional[str]: - return self.statusbar.prompt(s) - - -class CLIRepl(repl.Repl): - def __init__( - self, - scr: "_CursesWindow", - interp: repl.Interpreter, - statusbar: "Statusbar", - config: Config, - idle: Optional[Callable] = None, - ): - super().__init__(interp, config) - # mypy doesn't quite understand the difference between a class variable with a callable type and a method. - # https://github.com/python/mypy/issues/2427 - self.interp.writetb = self.writetb # type:ignore[assignment] - self.scr: "_CursesWindow" = scr - self.stdout_hist = "" # native str (bytes in Py2, unicode in Py3) - self.list_win = newwin(get_colpair(config, "background"), 1, 1, 1, 1) - self.cpos = 0 - self.do_exit = False - self.exit_value: Tuple[Any, ...] = () - self.f_string = "" - self.idle = idle - self.in_hist = False - self.paste_mode = False - self.last_key_press = time.time() - self.s = "" - self.statusbar = statusbar - self.formatter = BPythonFormatter(config.color_scheme) - self.interact = CLIInteraction(self.config, statusbar=self.statusbar) - self.ix: int - self.iy: int - self.arg_pos: Union[str, int, None] - self.prev_block_finished: int - - if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1: - config.cli_suggestion_width = 0.8 - - def _get_cursor_offset(self) -> int: - return len(self.s) - self.cpos - - def _set_cursor_offset(self, offset: int) -> None: - self.cpos = len(self.s) - offset - - def addstr(self, s: str) -> None: - """Add a string to the current input line and figure out - where it should go, depending on the cursor position.""" - self.rl_history.reset() - if not self.cpos: - self.s += s - else: - l = len(self.s) - self.s = self.s[: l - self.cpos] + s + self.s[l - self.cpos :] - - self.complete() - - def atbol(self) -> bool: - """Return True or False accordingly if the cursor is at the beginning - of the line (whitespace is ignored). This exists so that p_key() knows - how to handle the tab key being pressed - if there is nothing but white - space before the cursor then process it as a normal tab otherwise - attempt tab completion.""" - - return not self.s.lstrip() - - # This function shouldn't return None because of pos -= self.bs() later on - def bs(self, delete_tabs: bool = True) -> int: # type: ignore[return-value] - """Process a backspace""" - - self.rl_history.reset() - y, x = self.scr.getyx() - - if not self.s: - return None # type: ignore[return-value] - - if x == self.ix and y == self.iy: - return None # type: ignore[return-value] - - n = 1 - - self.clear_wrapped_lines() - - if not self.cpos: - # I know the nested if blocks look nasty. :( - if self.atbol() and delete_tabs: - n = len(self.s) % self.config.tab_length - if not n: - n = self.config.tab_length - - self.s = self.s[:-n] - else: - self.s = self.s[: -self.cpos - 1] + self.s[-self.cpos :] - - self.print_line(self.s, clr=True) - - return n - - def bs_word(self) -> str: - self.rl_history.reset() - pos = len(self.s) - self.cpos - 1 - deleted = [] - # First we delete any space to the left of the cursor. - while pos >= 0 and self.s[pos] == " ": - deleted.append(self.s[pos]) - pos -= self.bs() - # Then we delete a full word. - while pos >= 0 and self.s[pos] != " ": - deleted.append(self.s[pos]) - pos -= self.bs() - - return "".join(reversed(deleted)) - - def check(self) -> None: - """Check if paste mode should still be active and, if not, deactivate - it and force syntax highlighting.""" - - if ( - self.paste_mode - and time.time() - self.last_key_press > self.config.paste_time - ): - self.paste_mode = False - self.print_line(self.s) - - def clear_current_line(self) -> None: - """Called when a SyntaxError occurred in the interpreter. It is - used to prevent autoindentation from occurring after a - traceback.""" - repl.Repl.clear_current_line(self) - self.s = "" - - def clear_wrapped_lines(self) -> None: - """Clear the wrapped lines of the current input.""" - # curses does not handle this on its own. Sad. - height, width = self.scr.getmaxyx() - max_y = min(self.iy + (self.ix + len(self.s)) // width + 1, height) - for y in range(self.iy + 1, max_y): - self.scr.move(y, 0) - self.scr.clrtoeol() - - def complete(self, tab: bool = False) -> None: - """Get Autocomplete list and window. - - Called whenever these should be updated, and called - with tab - """ - if self.paste_mode: - self.scr.touchwin() # TODO necessary? - return - - list_win_visible = repl.Repl.complete(self, tab) - - if list_win_visible: - try: - f = None - if self.matches_iter.completer: - f = self.matches_iter.completer.format - - self.show_list( - self.matches_iter.matches, - self.arg_pos, - topline=self.funcprops, - formatter=f, - ) - except curses.error: - # XXX: This is a massive hack, it will go away when I get - # cusswords into a good enough state that we can start - # using it. - self.list_win.border() - self.list_win.refresh() - list_win_visible = False - if not list_win_visible: - self.scr.redrawwin() - self.scr.refresh() - - def clrtobol(self) -> None: - """Clear from cursor to beginning of line; usual C-u behaviour""" - self.clear_wrapped_lines() - - if not self.cpos: - self.s = "" - else: - self.s = self.s[-self.cpos :] - - self.print_line(self.s, clr=True) - self.scr.redrawwin() - self.scr.refresh() - - def _get_current_line(self) -> str: - return self.s - - def _set_current_line(self, line: str) -> None: - self.s = line - - def cut_to_buffer(self) -> None: - """Clear from cursor to end of line, placing into cut buffer""" - self.cut_buffer = self.s[-self.cpos :] - self.s = self.s[: -self.cpos] - self.cpos = 0 - self.print_line(self.s, clr=True) - self.scr.redrawwin() - self.scr.refresh() - - def delete(self) -> None: - """Process a del""" - if not self.s: - return - - if self.mvc(-1): - self.bs(False) - - def echo(self, s: str, redraw: bool = True) -> None: - """Parse and echo a formatted string with appropriate attributes. It - uses the formatting method as defined in formatter.py to parse the - strings. It won't update the screen if it's reevaluating the code (as it - does with undo).""" - a = get_colpair(self.config, "output") - if "\x01" in s: - rx = re.search("\x01([A-Za-z])([A-Za-z]?)", s) - if rx: - fg = rx.groups()[0] - bg = rx.groups()[1] - col_num = self._C[fg.lower()] - if bg and bg != "I": - col_num *= self._C[bg.lower()] - - a = curses.color_pair(int(col_num) + 1) - if bg == "I": - a = a | curses.A_REVERSE - s = re.sub("\x01[A-Za-z][A-Za-z]?", "", s) - if fg.isupper(): - a = a | curses.A_BOLD - s = s.replace("\x03", "") - s = s.replace("\x01", "") - - # Replace NUL bytes, as addstr raises an exception otherwise - s = s.replace("\0", "") - # Replace \r\n bytes, as addstr remove the current line otherwise - s = s.replace("\r\n", "\n") - - self.scr.addstr(s, a) - - if redraw and not self.evaluating: - self.scr.refresh() - - def end(self, refresh: bool = True) -> bool: - self.cpos = 0 - h, w = gethw() - y, x = divmod(len(self.s) + self.ix, w) - y += self.iy - self.scr.move(y, x) - if refresh: - self.scr.refresh() - - return True - - def hbegin(self) -> None: - """Replace the active line with first line in history and - increment the index to keep track""" - self.cpos = 0 - self.clear_wrapped_lines() - self.rl_history.enter(self.s) - self.s = self.rl_history.first() - self.print_line(self.s, clr=True) - - def hend(self) -> None: - """Same as hbegin() but, well, forward""" - self.cpos = 0 - self.clear_wrapped_lines() - self.rl_history.enter(self.s) - self.s = self.rl_history.last() - self.print_line(self.s, clr=True) - - def back(self) -> None: - """Replace the active line with previous line in history and - increment the index to keep track""" - - self.cpos = 0 - self.clear_wrapped_lines() - self.rl_history.enter(self.s) - self.s = self.rl_history.back() - self.print_line(self.s, clr=True) - - def fwd(self) -> None: - """Same as back() but, well, forward""" - - self.cpos = 0 - self.clear_wrapped_lines() - self.rl_history.enter(self.s) - self.s = self.rl_history.forward() - self.print_line(self.s, clr=True) - - def search(self) -> None: - """Search with the partial matches from the history object.""" - - self.cpo = 0 - self.clear_wrapped_lines() - self.rl_history.enter(self.s) - self.s = self.rl_history.back(start=False, search=True) - self.print_line(self.s, clr=True) - - def get_key(self) -> str: - key = "" - while True: - try: - key += self.scr.getkey() - # Seems like we get a in the locale's encoding - # encoded string in Python 3 as well, but of - # type str instead of bytes, hence convert it to - # bytes first and decode then - key = key.encode("latin-1").decode(getpreferredencoding()) - self.scr.nodelay(False) - except UnicodeDecodeError: - # Yes, that actually kind of sucks, but I don't see another way to get - # input right - self.scr.nodelay(True) - except curses.error: - # I'm quite annoyed with the ambiguity of this exception handler. I previously - # caught "curses.error, x" and accessed x.message and checked that it was "no - # input", which seemed a crappy way of doing it. But then I ran it on a - # different computer and the exception seems to have entirely different - # attributes. So let's hope getkey() doesn't raise any other crazy curses - # exceptions. :) - self.scr.nodelay(False) - # XXX What to do here? Raise an exception? - if key: - return key - else: - if key != "\x00": - t = time.time() - self.paste_mode = ( - t - self.last_key_press <= self.config.paste_time - ) - self.last_key_press = t - return key - else: - key = "" - finally: - if self.idle: - self.idle(self) - - def get_line(self) -> str: - """Get a line of text and return it - This function initialises an empty string and gets the - curses cursor position on the screen and stores it - for the echo() function to use later (I think). - Then it waits for key presses and passes them to p_key(), - which returns None if Enter is pressed (that means "Return", - idiot).""" - - self.s = "" - self.rl_history.reset() - self.iy, self.ix = self.scr.getyx() - - if not self.paste_mode: - for _ in range(self.next_indentation()): - self.p_key("\t") - - self.cpos = 0 - - while True: - key = self.get_key() - if self.p_key(key) is None: - if self.config.cli_trim_prompts and self.s.startswith(">>> "): - self.s = self.s[4:] - return self.s - - def home(self, refresh: bool = True) -> bool: - self.scr.move(self.iy, self.ix) - self.cpos = len(self.s) - if refresh: - self.scr.refresh() - return True - - def lf(self) -> None: - """Process a linefeed character; it only needs to check the - cursor position and move appropriately so it doesn't clear - the current line after the cursor.""" - if self.cpos: - for _ in range(self.cpos): - self.mvc(-1) - - # Reprint the line (as there was maybe a highlighted paren in it) - self.print_line(self.s, newline=True) - self.echo("\n") - - def mkargspec( - self, - topline: inspection.FuncProps, - in_arg: Union[str, int, None], - down: bool, - ) -> int: - """This figures out what to do with the argspec and puts it nicely into - the list window. It returns the number of lines used to display the - argspec. It's also kind of messy due to it having to call so many - addstr() to get the colouring right, but it seems to be pretty - sturdy.""" - - r = 3 - fn = topline.func - args = topline.argspec.args - kwargs = topline.argspec.defaults - _args = topline.argspec.varargs - _kwargs = topline.argspec.varkwargs - is_bound_method = topline.is_bound_method - kwonly = topline.argspec.kwonly - kwonly_defaults = topline.argspec.kwonly_defaults or dict() - max_w = int(self.scr.getmaxyx()[1] * 0.6) - self.list_win.erase() - self.list_win.resize(3, max_w) - h, w = self.list_win.getmaxyx() - - self.list_win.addstr("\n ") - self.list_win.addstr( - fn, get_colpair(self.config, "name") | curses.A_BOLD - ) - self.list_win.addstr(": (", get_colpair(self.config, "name")) - maxh = self.scr.getmaxyx()[0] - - if is_bound_method and isinstance(in_arg, int): - in_arg += 1 - - punctuation_colpair = get_colpair(self.config, "punctuation") - - for k, i in enumerate(args): - y, x = self.list_win.getyx() - ln = len(str(i)) - kw = None - if kwargs and k + 1 > len(args) - len(kwargs): - kw = repr(kwargs[k - (len(args) - len(kwargs))]) - ln += len(kw) + 1 - - if ln + x >= w: - ty = self.list_win.getbegyx()[0] - if not down and ty > 0: - h += 1 - self.list_win.mvwin(ty - 1, 1) - self.list_win.resize(h, w) - elif down and h + r < maxh - ty: - h += 1 - self.list_win.resize(h, w) - else: - break - r += 1 - self.list_win.addstr("\n\t") - - if str(i) == "self" and k == 0: - color = get_colpair(self.config, "name") - else: - color = get_colpair(self.config, "token") - - if k == in_arg or i == in_arg: - color |= curses.A_BOLD - - self.list_win.addstr(str(i), color) - if kw is not None: - self.list_win.addstr("=", punctuation_colpair) - self.list_win.addstr(kw, get_colpair(self.config, "token")) - if k != len(args) - 1: - self.list_win.addstr(", ", punctuation_colpair) - - if _args: - if args: - self.list_win.addstr(", ", punctuation_colpair) - self.list_win.addstr(f"*{_args}", get_colpair(self.config, "token")) - - if kwonly: - if not _args: - if args: - self.list_win.addstr(", ", punctuation_colpair) - self.list_win.addstr("*", punctuation_colpair) - marker = object() - for arg in kwonly: - self.list_win.addstr(", ", punctuation_colpair) - color = get_colpair(self.config, "token") - if arg == in_arg: - color |= curses.A_BOLD - self.list_win.addstr(arg, color) - default = kwonly_defaults.get(arg, marker) - if default is not marker: - self.list_win.addstr("=", punctuation_colpair) - self.list_win.addstr( - repr(default), get_colpair(self.config, "token") - ) - - if _kwargs: - if args or _args or kwonly: - self.list_win.addstr(", ", punctuation_colpair) - self.list_win.addstr( - f"**{_kwargs}", get_colpair(self.config, "token") - ) - self.list_win.addstr(")", punctuation_colpair) - - return r - - def mvc(self, i: int, refresh: bool = True) -> bool: - """This method moves the cursor relatively from the current - position, where: - 0 == (right) end of current line - length of current line len(self.s) == beginning of current line - and: - current cursor position + i - for positive values of i the cursor will move towards the beginning - of the line, negative values the opposite.""" - y, x = self.scr.getyx() - - if self.cpos == 0 and i < 0: - return False - - if x == self.ix and y == self.iy and i >= 1: - return False - - h, w = gethw() - if x - i < 0: - y -= 1 - x = w - - if x - i >= w: - y += 1 - x = 0 + i - - self.cpos += i - self.scr.move(y, x - i) - if refresh: - self.scr.refresh() - - return True - - def p_key(self, key: str) -> Union[None, str, bool]: - """Process a keypress""" - - if key is None: - return "" - - config = self.config - - if platform.system() == "Windows": - C_BACK = chr(127) - BACKSP = chr(8) - else: - C_BACK = chr(8) - BACKSP = chr(127) - - if key == C_BACK: # C-Backspace (on my computer anyway!) - self.clrtobol() - key = "\n" - # Don't return; let it get handled - - if key == chr(27): # Escape Key - return "" - - if key in (BACKSP, "KEY_BACKSPACE"): - self.bs() - self.complete() - return "" - - elif key in key_dispatch[config.delete_key] and not self.s: - # Delete on empty line exits - self.do_exit = True - return None - - elif key in ("KEY_DC",) + key_dispatch[config.delete_key]: - self.delete() - self.complete() - # Redraw (as there might have been highlighted parens) - self.print_line(self.s) - return "" - - elif key in key_dispatch[config.undo_key]: # C-r - n = self.prompt_undo() - if n > 0: - self.undo(n=n) - return "" - - elif key in key_dispatch[config.search_key]: - self.search() - return "" - - elif key in ("KEY_UP",) + key_dispatch[config.up_one_line_key]: - # Cursor Up/C-p - self.back() - return "" - - elif key in ("KEY_DOWN",) + key_dispatch[config.down_one_line_key]: - # Cursor Down/C-n - self.fwd() - return "" - - elif key in ("KEY_LEFT", " ^B", chr(2)): # Cursor Left or ^B - self.mvc(1) - # Redraw (as there might have been highlighted parens) - self.print_line(self.s) - - elif key in ("KEY_RIGHT", "^F", chr(6)): # Cursor Right or ^F - self.mvc(-1) - # Redraw (as there might have been highlighted parens) - self.print_line(self.s) - - elif key in ("KEY_HOME", "^A", chr(1)): # home or ^A - self.home() - # Redraw (as there might have been highlighted parens) - self.print_line(self.s) - - elif key in ("KEY_END", "^E", chr(5)): # end or ^E - self.end() - # Redraw (as there might have been highlighted parens) - self.print_line(self.s) - - elif key in ("KEY_NPAGE",): # page_down - self.hend() - self.print_line(self.s) - - elif key in ("KEY_PPAGE",): # page_up - self.hbegin() - self.print_line(self.s) - - elif key in key_dispatch[config.cut_to_buffer_key]: # cut to buffer - self.cut_to_buffer() - return "" - - elif key in key_dispatch[config.yank_from_buffer_key]: - # yank from buffer - self.yank_from_buffer() - return "" - - elif key in key_dispatch[config.clear_word_key]: - self.cut_buffer = self.bs_word() - self.complete() - return "" - - elif key in key_dispatch[config.clear_line_key]: - self.clrtobol() - return "" - - elif key in key_dispatch[config.clear_screen_key]: - # clear all but current line - self.screen_hist: List = [self.screen_hist[-1]] - self.highlighted_paren = None - self.redraw() - return "" - - elif key in key_dispatch[config.exit_key]: - if not self.s: - self.do_exit = True - return None - else: - return "" - - elif key in key_dispatch[config.save_key]: - self.write2file() - return "" - - elif key in key_dispatch[config.pastebin_key]: - self.pastebin() - return "" - - elif key in key_dispatch[config.copy_clipboard_key]: - self.copy2clipboard() - return "" - - elif key in key_dispatch[config.last_output_key]: - page(self.stdout_hist[self.prev_block_finished : -4]) - return "" - - elif key in key_dispatch[config.show_source_key]: - try: - source = self.get_source_of_current_name() - except repl.SourceNotFound as e: - self.statusbar.message(f"{e}") - else: - if config.highlight_show_source: - source = format( - Python3Lexer().get_tokens(source), TerminalFormatter() - ) - page(source) - return "" - - elif key in ("\n", "\r", "PADENTER"): - self.lf() - return None - - elif key == "\t": - return self.tab() - - elif key == "KEY_BTAB": - return self.tab(back=True) - - elif key in key_dispatch[config.suspend_key]: - if platform.system() != "Windows": - self.suspend() - return "" - else: - self.do_exit = True - return None - - elif key == "\x18": - return self.send_current_line_to_editor() - - elif key == "\x03": - raise KeyboardInterrupt() - - elif key[0:3] == "PAD" and key not in ("PAD0", "PADSTOP"): - pad_keys = { - "PADMINUS": "-", - "PADPLUS": "+", - "PADSLASH": "/", - "PADSTAR": "*", - } - try: - self.addstr(pad_keys[key]) - self.print_line(self.s) - except KeyError: - return "" - elif len(key) == 1 and not unicodedata.category(key) == "Cc": - self.addstr(key) - self.print_line(self.s) - - else: - return "" - - return True - - def print_line( - self, s: Optional[str], clr: bool = False, newline: bool = False - ) -> None: - """Chuck a line of text through the highlighter, move the cursor - to the beginning of the line and output it to the screen.""" - - if not s: - clr = True - - if self.highlighted_paren is not None: - # Clear previous highlighted paren - - lineno = self.highlighted_paren[0] - tokens = self.highlighted_paren[1] - # mypy thinks tokens is List[Tuple[_TokenType, str]] - # but it is supposed to be MutableMapping[_TokenType, str] - self.reprint_line(lineno, tokens) - self.highlighted_paren = None - - if self.config.syntax and (not self.paste_mode or newline): - o = format(self.tokenize(s, newline), self.formatter) - else: - o = s - - self.f_string = o - self.scr.move(self.iy, self.ix) - - if clr: - self.scr.clrtoeol() - - if clr and not s: - self.scr.refresh() - - if o: - for t in o.split("\x04"): - self.echo(t.rstrip("\n")) - - if self.cpos: - t = self.cpos - for _ in range(self.cpos): - self.mvc(1) - self.cpos = t - - def prompt(self, more: Any) -> None: # I'm not sure of the type on this one - """Show the appropriate Python prompt""" - if not more: - self.echo( - "\x01{}\x03{}".format( - self.config.color_scheme["prompt"], self.ps1 - ) - ) - self.stdout_hist += self.ps1 - self.screen_hist.append( - "\x01%s\x03%s\x04" - % (self.config.color_scheme["prompt"], self.ps1) - ) - else: - prompt_more_color = self.config.color_scheme["prompt_more"] - self.echo(f"\x01{prompt_more_color}\x03{self.ps2}") - self.stdout_hist += self.ps2 - self.screen_hist.append( - f"\x01{prompt_more_color}\x03{self.ps2}\x04" - ) - - def push(self, s: str, insert_into_history: bool = True) -> bool: - # curses.raw(True) prevents C-c from causing a SIGINT - curses.raw(False) - try: - return super().push(s, insert_into_history) - except SystemExit as e: - # Avoid a traceback on e.g. quit() - self.do_exit = True - self.exit_value = e.args - return False - finally: - curses.raw(True) - - def redraw(self) -> None: - """Redraw the screen using screen_hist""" - self.scr.erase() - for k, s in enumerate(self.screen_hist): - if not s: - continue - self.iy, self.ix = self.scr.getyx() - for i in s.split("\x04"): - self.echo(i, redraw=False) - if k < len(self.screen_hist) - 1: - self.scr.addstr("\n") - self.iy, self.ix = self.scr.getyx() - self.print_line(self.s) - self.scr.refresh() - self.statusbar.refresh() - - def repl(self) -> Tuple[Any, ...]: - """Initialise the repl and jump into the loop. This method also has to - keep a stack of lines entered for the horrible "undo" feature. It also - tracks everything that would normally go to stdout in the normal Python - interpreter so it can quickly write it to stdout on exit after - curses.endwin(), as well as a history of lines entered for using - up/down to go back and forth (which has to be separate to the - evaluation history, which will be truncated when undoing.""" - - # Use our own helper function because Python's will use real stdin and - # stdout instead of our wrapped - self.push("from bpython._internal import _help as help\n", False) - - self.iy, self.ix = self.scr.getyx() - self.more = False - while not self.do_exit: - self.f_string = "" - self.prompt(self.more) - try: - inp = self.get_line() - except KeyboardInterrupt: - self.statusbar.message("KeyboardInterrupt") - self.scr.addstr("\n") - self.scr.touchwin() - self.scr.refresh() - continue - - self.scr.redrawwin() - if self.do_exit: - return self.exit_value - - self.history.append(inp) - self.screen_hist[-1] += self.f_string - self.stdout_hist += inp + "\n" - stdout_position = len(self.stdout_hist) - self.more = self.push(inp) - if not self.more: - self.prev_block_finished = stdout_position - self.s = "" - return self.exit_value - - def reprint_line( - self, lineno: int, tokens: List[Tuple[_TokenType, str]] - ) -> None: - """Helper function for paren highlighting: Reprint line at offset - `lineno` in current input buffer.""" - if not self.buffer or lineno == len(self.buffer): - return - - real_lineno = self.iy - height, width = self.scr.getmaxyx() - for i in range(lineno, len(self.buffer)): - string = self.buffer[i] - # 4 = length of prompt - length = len(string.encode(getpreferredencoding())) + 4 - real_lineno -= int(math.ceil(length / width)) - if real_lineno < 0: - return - - self.scr.move( - real_lineno, len(self.ps1) if lineno == 0 else len(self.ps2) - ) - line = format(tokens, BPythonFormatter(self.config.color_scheme)) - for string in line.split("\x04"): - self.echo(string) - - def resize(self) -> None: - """This method exists simply to keep it straight forward when - initialising a window and resizing it.""" - self.size() - self.scr.erase() - self.scr.resize(self.h, self.w) - self.scr.mvwin(self.y, self.x) - self.statusbar.resize(refresh=False) - self.redraw() - - def getstdout(self) -> str: - """This method returns the 'spoofed' stdout buffer, for writing to a - file or sending to a pastebin or whatever.""" - - return self.stdout_hist + "\n" - - def reevaluate(self) -> None: - """Clear the buffer, redraw the screen and re-evaluate the history""" - - self.evaluating = True - self.stdout_hist = "" - self.f_string = "" - self.buffer: List[str] = [] - self.scr.erase() - self.screen_hist = [] - # Set cursor position to -1 to prevent paren matching - self.cpos = -1 - - self.prompt(False) - - self.iy, self.ix = self.scr.getyx() - for line in self.history: - self.stdout_hist += line + "\n" - self.print_line(line) - self.screen_hist[-1] += self.f_string - # I decided it was easier to just do this manually - # than to make the print_line and history stuff more flexible. - self.scr.addstr("\n") - self.more = self.push(line) - self.prompt(self.more) - self.iy, self.ix = self.scr.getyx() - - self.cpos = 0 - indent = repl.next_indentation(self.s, self.config.tab_length) - self.s = "" - self.scr.refresh() - - if self.buffer: - for _ in range(indent): - self.tab() - - self.evaluating = False - # map(self.push, self.history) - # ^-- That's how simple this method was at first :( - - def write(self, s: str) -> None: - """For overriding stdout defaults""" - if "\x04" in s: - for block in s.split("\x04"): - self.write(block) - return - if s.rstrip() and "\x03" in s: - t = s.split("\x03")[1] - else: - t = s - - if not self.stdout_hist: - self.stdout_hist = t - else: - self.stdout_hist += t - - self.echo(s) - self.screen_hist.append(s.rstrip()) - - def show_list( - self, - items: List[str], - arg_pos: Union[str, int, None], - topline: Optional[inspection.FuncProps] = None, - formatter: Optional[Callable] = None, - current_item: Optional[str] = None, - ) -> None: - v_items: Collection - shared = ShowListState() - y, x = self.scr.getyx() - h, w = self.scr.getmaxyx() - down = y < h // 2 - if down: - max_h = h - y - else: - max_h = y + 1 - max_w = int(w * self.config.cli_suggestion_width) - self.list_win.erase() - - if items and formatter: - items = [formatter(x) for x in items] - if current_item is not None: - current_item = formatter(current_item) - - if topline: - height_offset = self.mkargspec(topline, arg_pos, down) + 1 - else: - height_offset = 0 - - def lsize() -> bool: - wl = max(len(i) for i in v_items) + 1 - if not wl: - wl = 1 - cols = ((max_w - 2) // wl) or 1 - rows = len(v_items) // cols - - if cols * rows < len(v_items): - rows += 1 - - if rows + 2 >= max_h: - return False - - shared.rows = rows - shared.cols = cols - shared.wl = wl - return True - - if items: - # visible items (we'll append until we can't fit any more in) - v_items = [items[0][: max_w - 3]] - lsize() - else: - v_items = [] - - for i in items[1:]: - v_items.append(i[: max_w - 3]) - if not lsize(): - del v_items[-1] - v_items[-1] = "..." - break - - rows = shared.rows - if rows + height_offset < max_h: - rows += height_offset - display_rows = rows - else: - display_rows = rows + height_offset - - cols = shared.cols - wl = shared.wl - - if topline and not v_items: - w = max_w - elif wl + 3 > max_w: - w = max_w - else: - t = (cols + 1) * wl + 3 - if t > max_w: - t = max_w - w = t - - if height_offset and display_rows + 5 >= max_h: - del v_items[-(cols * (height_offset)) :] - - if self.docstring is None: - self.list_win.resize(rows + 2, w) - else: - docstring = self.format_docstring( - self.docstring, max_w - 2, max_h - height_offset - ) - docstring_string = "".join(docstring) - rows += len(docstring) - self.list_win.resize(rows, max_w) - - if down: - self.list_win.mvwin(y + 1, 0) - else: - self.list_win.mvwin(y - rows - 2, 0) - - if v_items: - self.list_win.addstr("\n ") - - for ix, i in enumerate(v_items): - padding = (wl - len(i)) * " " - if i == current_item: - color = get_colpair(self.config, "operator") - else: - color = get_colpair(self.config, "main") - self.list_win.addstr(i + padding, color) - if (cols == 1 or (ix and not (ix + 1) % cols)) and ix + 1 < len( - v_items - ): - self.list_win.addstr("\n ") - - if self.docstring is not None: - self.list_win.addstr( - "\n" + docstring_string, get_colpair(self.config, "comment") - ) - # XXX: After all the trouble I had with sizing the list box (I'm not very good - # at that type of thing) I decided to do this bit of tidying up here just to - # make sure there's no unnecessary blank lines, it makes things look nicer. - - y = self.list_win.getyx()[0] - self.list_win.resize(y + 2, w) - - self.statusbar.win.touchwin() - self.statusbar.win.noutrefresh() - self.list_win.attron(get_colpair(self.config, "main")) - self.list_win.border() - self.scr.touchwin() - self.scr.cursyncup() - self.scr.noutrefresh() - - # This looks a little odd, but I can't figure a better way to stick the cursor - # back where it belongs (refreshing the window hides the list_win) - - self.scr.move(*self.scr.getyx()) - self.list_win.refresh() - - def size(self) -> None: - """Set instance attributes for x and y top left corner coordinates - and width and height for the window.""" - global stdscr - if stdscr: - h, w = stdscr.getmaxyx() - self.y: int = 0 - self.w: int = w - self.h: int = h - 1 - self.x: int = 0 - - def suspend(self) -> None: - """Suspend the current process for shell job control.""" - if platform.system() != "Windows": - curses.endwin() - os.kill(os.getpid(), signal.SIGSTOP) - - def tab(self, back: bool = False) -> bool: - """Process the tab key being hit. - - If there's only whitespace - in the line or the line is blank then process a normal tab, - otherwise attempt to autocomplete to the best match of possible - choices in the match list. - - If `back` is True, walk backwards through the list of suggestions - and don't indent if there are only whitespace in the line. - """ - - # 1. check if we should add a tab character - if self.atbol() and not back: - x_pos = len(self.s) - self.cpos - num_spaces = x_pos % self.config.tab_length - if not num_spaces: - num_spaces = self.config.tab_length - - self.addstr(" " * num_spaces) - self.print_line(self.s) - return True - - # 2. run complete() if we aren't already iterating through matches - if not self.matches_iter: - self.complete(tab=True) - self.print_line(self.s) - - # 3. check to see if we can expand the current word - if self.matches_iter.is_cseq(): - # TODO resolve this error-prone situation: - # can't assign at same time to self.s and self.cursor_offset - # because for cursor_offset - # property to work correctly, self.s must already be set - temp_cursor_offset, self.s = self.matches_iter.substitute_cseq() - self.cursor_offset = temp_cursor_offset - self.print_line(self.s) - if not self.matches_iter: - self.complete() - - # 4. swap current word for a match list item - elif self.matches_iter.matches: - current_match = ( - self.matches_iter.previous() - if back - else next(self.matches_iter) - ) - try: - f = None - if self.matches_iter.completer: - f = self.matches_iter.completer.format - - self.show_list( - self.matches_iter.matches, - self.arg_pos, - topline=self.funcprops, - formatter=f, - current_item=current_match, - ) - except curses.error: - # XXX: This is a massive hack, it will go away when I get - # cusswords into a good enough state that we can start - # using it. - self.list_win.border() - self.list_win.refresh() - _, self.s = self.matches_iter.cur_line() - self.print_line(self.s, True) - return True - - def undo(self, n: int = 1) -> None: - repl.Repl.undo(self, n) - - # This will unhighlight highlighted parens - self.print_line(self.s) - - def writetb(self, lines: List[str]) -> None: - for line in lines: - self.write( - "\x01{}\x03{}".format(self.config.color_scheme["error"], line) - ) - - def yank_from_buffer(self) -> None: - """Paste the text from the cut buffer at the current cursor location""" - self.addstr(self.cut_buffer) - self.print_line(self.s, clr=True) - - def send_current_line_to_editor(self) -> str: - lines = self.send_to_external_editor(self.s).split("\n") - self.s = "" - self.print_line(self.s) - while lines and not lines[-1]: - lines.pop() - if not lines: - return "" - - self.f_string = "" - self.cpos = -1 # Set cursor position to -1 to prevent paren matching - - self.iy, self.ix = self.scr.getyx() - self.evaluating = True - for line in lines: - self.stdout_hist += line + "\n" - self.history.append(line) - self.print_line(line) - self.screen_hist[-1] += self.f_string - self.scr.addstr("\n") - self.more = self.push(line) - self.prompt(self.more) - self.iy, self.ix = self.scr.getyx() - self.evaluating = False - - self.cpos = 0 - indent = repl.next_indentation(self.s, self.config.tab_length) - self.s = "" - self.scr.refresh() - - if self.buffer: - for _ in range(indent): - self.tab() - - self.print_line(self.s) - self.scr.redrawwin() - return "" - - -class Statusbar: - """This class provides the status bar at the bottom of the screen. - It has message() and prompt() methods for user interactivity, as - well as settext() and clear() methods for changing its appearance. - - The check() method needs to be called repeatedly if the statusbar is - going to be aware of when it should update its display after a message() - has been called (it'll display for a couple of seconds and then disappear). - - It should be called as: - foo = Statusbar(stdscr, scr, 'Initial text to display') - or, for a blank statusbar: - foo = Statusbar(stdscr, scr) - - It can also receive the argument 'c' which will be an integer referring - to a curses colour pair, e.g.: - foo = Statusbar(stdscr, 'Hello', c=4) - - stdscr should be a curses window object in which to put the status bar. - pwin should be the parent window. To be honest, this is only really here - so the cursor can be returned to the window properly. - - """ - - def __init__( - self, - scr: "_CursesWindow", - pwin: "_CursesWindow", - background: int, - config: Config, - s: Optional[str] = None, - c: Optional[int] = None, - ): - """Initialise the statusbar and display the initial text (if any)""" - self.size() - self.win: "_CursesWindow" = newwin( - background, self.h, self.w, self.y, self.x - ) - - self.config = config - - self.s = s or "" - self._s = self.s - self.c = c - self.timer = 0 - self.pwin = pwin - if s: - self.settext(s, c) - - def size(self) -> None: - """Set instance attributes for x and y top left corner coordinates - and width and height for the window.""" - h, w = gethw() - self.y = h - 1 - self.w = w - self.h = 1 - self.x = 0 - - def resize(self, refresh: bool = True) -> None: - """This method exists simply to keep it straight forward when - initialising a window and resizing it.""" - self.size() - self.win.mvwin(self.y, self.x) - self.win.resize(self.h, self.w) - if refresh: - self.refresh() - - def refresh(self) -> None: - """This is here to make sure the status bar text is redraw properly - after a resize.""" - self.settext(self._s) - - def check(self) -> None: - """This is the method that should be called every half second or so - to see if the status bar needs updating.""" - if not self.timer: - return - - if time.time() < self.timer: - return - - self.settext(self._s) - - def message(self, s: str, n: float = 3.0) -> None: - """Display a message for a short n seconds on the statusbar and return - it to its original state.""" - self.timer = int(time.time() + n) - self.settext(s) - - def prompt(self, s: str = "") -> str: - """Prompt the user for some input (with the optional prompt 's') and - return the input text, then restore the statusbar to its original - value.""" - - self.settext(s or "? ", p=True) - iy, ix = self.win.getyx() - - def bs(s: str) -> str: - y, x = self.win.getyx() - if x == ix: - return s - s = s[:-1] - self.win.delch(y, x - 1) - self.win.move(y, x - 1) - return s - - o = "" - while True: - c = self.win.getch() - - # '\b' - if c == 127: - o = bs(o) - # '\n' - elif c == 10: - break - # ESC - elif c == 27: - curses.flushinp() - raise ValueError - # literal - elif 0 < c < 127: - d = chr(c) - self.win.addstr(d, get_colpair(self.config, "prompt")) - o += d - - self.settext(self._s) - return o - - def settext(self, s: str, c: Optional[int] = None, p: bool = False) -> None: - """Set the text on the status bar to a new permanent value; this is the - value that will be set after a prompt or message. c is the optional - curses colour pair to use (if not specified the last specified colour - pair will be used). p is True if the cursor is expected to stay in the - status window (e.g. when prompting).""" - - self.win.erase() - if len(s) >= self.w: - s = s[: self.w - 1] - - self.s = s - if c: - self.c = c - - if s: - if self.c: - self.win.addstr(s, self.c) - else: - self.win.addstr(s) - - if not p: - self.win.noutrefresh() - self.pwin.refresh() - else: - self.win.refresh() - - def clear(self) -> None: - """Clear the status bar.""" - self.win.clear() - - -def init_wins( - scr: "_CursesWindow", config: Config -) -> Tuple["_CursesWindow", Statusbar]: - """Initialise the two windows (the main repl interface and the little - status bar at the bottom with some stuff in it)""" - # TODO: Document better what stuff is on the status bar. - - background = get_colpair(config, "background") - h, w = gethw() - - main_win = newwin(background, h - 1, w, 0, 0) - main_win.scrollok(True) - - # I think this is supposed to be True instead of 1? - main_win.keypad(1) # type:ignore[arg-type] - # Thanks to Angus Gibson for pointing out this missing line which was causing - # problems that needed dirty hackery to fix. :) - - commands = ( - (_("Rewind"), config.undo_key), - (_("Save"), config.save_key), - (_("Pastebin"), config.pastebin_key), - (_("Pager"), config.last_output_key), - (_("Show Source"), config.show_source_key), - ) - - message = " ".join( - f"<{key}> {command}" for command, key in commands if key - ) - - statusbar = Statusbar( - scr, main_win, background, config, message, get_colpair(config, "main") - ) - - return main_win, statusbar - - -def sigwinch(unused_scr: "_CursesWindow") -> None: - global DO_RESIZE - DO_RESIZE = True - - -def sigcont(unused_scr: "_CursesWindow") -> None: - sigwinch(unused_scr) - # Forces the redraw - curses.ungetch("\x00") - - -def gethw() -> Tuple[int, int]: - """I found this code on a usenet post, and snipped out the bit I needed, - so thanks to whoever wrote that, sorry I forgot your name, I'm sure you're - a great guy. - - It's unfortunately necessary (unless someone has any better ideas) in order - to allow curses and readline to work together. I looked at the code for - libreadline and noticed this comment: - - /* This is the stuff that is hard for me. I never seem to write good - display routines in C. Let's see how I do this time. */ - - So I'm not going to ask any questions. - - """ - - if platform.system() != "Windows": - h, w = struct.unpack( - "hhhh", - fcntl.ioctl( - sys.__stdout__, termios.TIOCGWINSZ, "\000" * 8 - ), # type:ignore[call-overload] - )[0:2] - else: - # Ignoring mypy's windll error because it's Windows-specific - from ctypes import ( # type:ignore[attr-defined] - windll, - create_string_buffer, - ) - - # stdin handle is -10 - # stdout handle is -11 - # stderr handle is -12 - - h = windll.kernel32.GetStdHandle(-12) - csbi = create_string_buffer(22) - res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) - - if res: - ( - bufx, - bufy, - curx, - cury, - wattr, - left, - top, - right, - bottom, - maxx, - maxy, - ) = struct.unpack("hhhhHhhhhhh", csbi.raw) - sizex = right - left + 1 - sizey = bottom - top + 1 - elif stdscr: - # can't determine actual size - return default values - sizex, sizey = stdscr.getmaxyx() - - h, w = sizey, sizex - return h, w - - -def idle(caller: CLIRepl) -> None: - """This is called once every iteration through the getkey() - loop (currently in the Repl class, see the get_line() method). - The statusbar check needs to go here to take care of timed - messages and the resize handlers need to be here to make - sure it happens conveniently.""" - global DO_RESIZE - - if caller.module_gatherer.find_coroutine() or caller.paste_mode: - caller.scr.nodelay(True) - key = caller.scr.getch() - caller.scr.nodelay(False) - if key != -1: - curses.ungetch(key) - else: - curses.ungetch("\x00") - caller.statusbar.check() - caller.check() - - if DO_RESIZE: - do_resize(caller) - - -def do_resize(caller: CLIRepl) -> None: - """This needs to hack around readline and curses not playing - nicely together. See also gethw() above.""" - global DO_RESIZE - h, w = gethw() - if not h: - # Hopefully this shouldn't happen. :) - return - - curses.endwin() - os.environ["LINES"] = str(h) - os.environ["COLUMNS"] = str(w) - curses.doupdate() - DO_RESIZE = False - - try: - caller.resize() - except curses.error: - pass - # The list win resizes itself every time it appears so no need to do it here. - - -class FakeDict: - """Very simple dict-alike that returns a constant value for any key - - used as a hacky solution to using a colours dict containing colour codes if - colour initialisation fails.""" - - def __init__(self, val: int): - self._val = val - - def __getitem__(self, k: Any) -> int: - return self._val - - -def newwin(background: int, *args: int) -> "_CursesWindow": - """Wrapper for curses.newwin to automatically set background colour on any - newly created window.""" - win = curses.newwin(*args) - win.bkgd(" ", background) - return win - - -def curses_wrapper(func: Callable, *args: Any, **kwargs: Any) -> Any: - """Like curses.wrapper(), but reuses stdscr when called again.""" - global stdscr - if stdscr is None: - stdscr = curses.initscr() - try: - curses.noecho() - curses.cbreak() - # Should this be keypad(True)? - stdscr.keypad(1) # type:ignore[arg-type] - - try: - curses.start_color() - except curses.error: - pass - - return func(stdscr, *args, **kwargs) - finally: - # Should this be keypad(False)? - stdscr.keypad(0) # type:ignore[arg-type] - curses.echo() - curses.nocbreak() - curses.endwin() - - -def main_curses( - scr: "_CursesWindow", - args: List[str], - config: Config, - interactive: bool = True, - locals_: Optional[Dict[str, Any]] = None, - banner: Optional[str] = None, -) -> Tuple[Tuple[Any, ...], str]: - """main function for the curses convenience wrapper - - Initialise the two main objects: the interpreter - and the repl. The repl does what a repl does and lots - of other cool stuff like syntax highlighting and stuff. - I've tried to keep it well factored but it needs some - tidying up, especially in separating the curses stuff - from the rest of the repl. - - Returns a tuple (exit value, output), where exit value is a tuple - with arguments passed to SystemExit. - """ - global stdscr - global DO_RESIZE - global colors - DO_RESIZE = False - - if platform.system() != "Windows": - old_sigwinch_handler = signal.signal( - signal.SIGWINCH, lambda *_: sigwinch(scr) - ) - # redraw window after being suspended - old_sigcont_handler = signal.signal( - signal.SIGCONT, lambda *_: sigcont(scr) - ) - - stdscr = scr - try: - curses.start_color() - curses.use_default_colors() - cols = make_colors(config) - except curses.error: - # Not sure what to do with the types here... - # FakeDict acts as a dictionary, but isn't actually a dictionary - cols = FakeDict(-1) # type:ignore[assignment] - - # FIXME: Gargh, bad design results in using globals without a refactor :( - colors = cols - - scr.timeout(300) - - curses.raw(True) - main_win, statusbar = init_wins(scr, config) - - interpreter = repl.Interpreter(locals_) - - clirepl = CLIRepl(main_win, interpreter, statusbar, config, idle) - clirepl._C = cols - - # Not sure how to type these Fake types - sys.stdin = FakeStdin(clirepl) # type:ignore[assignment] - sys.stdout = FakeStream(clirepl, lambda: sys.stdout) # type:ignore - sys.stderr = FakeStream(clirepl, lambda: sys.stderr) # type:ignore - - if args: - exit_value: Tuple[Any, ...] = () - try: - bpargs.exec_code(interpreter, args) - except SystemExit as e: - # The documentation of code.InteractiveInterpreter.runcode claims - # that it reraises SystemExit. However, I can't manage to trigger - # that. To be one the safe side let's catch SystemExit here anyway. - exit_value = e.args - if not interactive: - curses.raw(False) - return (exit_value, clirepl.getstdout()) - else: - sys.path.insert(0, "") - try: - clirepl.startup() - except OSError as e: - # Handle this with a proper error message. - if e.errno != errno.ENOENT: - raise - - if banner is not None: - clirepl.write(banner) - clirepl.write("\n") - - # XXX these deprecation warnings need to go at some point - clirepl.write( - _( - "WARNING: You are using `bpython-cli`, the curses backend for `bpython`. This backend has been deprecated in version 0.19 and might disappear in a future version." - ) - ) - clirepl.write("\n") - - exit_value = clirepl.repl() - if hasattr(sys, "exitfunc"): - # Seems like the if statement should satisfy mypy, but it doesn't - sys.exitfunc() # type:ignore[attr-defined] - delattr(sys, "exitfunc") - - main_win.erase() - main_win.refresh() - statusbar.win.clear() - statusbar.win.refresh() - curses.raw(False) - - # Restore signal handlers - if platform.system() != "Windows": - signal.signal(signal.SIGWINCH, old_sigwinch_handler) - signal.signal(signal.SIGCONT, old_sigcont_handler) - - return (exit_value, clirepl.getstdout()) - - -def main( - args: Optional[List[str]] = None, - locals_: Optional[MutableMapping[str, str]] = None, - banner: Optional[str] = None, -) -> Any: - translations.init() - - config, options, exec_args = argsparse(args) - - # Save stdin, stdout and stderr for later restoration - orig_stdin = sys.stdin - orig_stdout = sys.stdout - orig_stderr = sys.stderr - - try: - (exit_value, output) = curses_wrapper( - main_curses, - exec_args, - config, - options.interactive, - locals_, - banner=banner, - ) - finally: - sys.stdin = orig_stdin - sys.stderr = orig_stderr - sys.stdout = orig_stdout - - # Fake stdout data so everything's still visible after exiting - if config.flush_output and not options.quiet: - sys.stdout.write(output) - if hasattr(sys.stdout, "flush"): - sys.stdout.flush() - return repl.extract_exit_value(exit_value) - - -if __name__ == "__main__": - sys.exit(main()) - -# vim: sw=4 ts=4 sts=4 ai et diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index b3e8912fd..74f4b7217 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -10,7 +10,7 @@ from pathlib import Path from unittest import mock -from bpython import config, repl, cli, autocomplete +from bpython import config, repl, autocomplete from bpython.line import LinePart from bpython.test import ( MagicIterMock, @@ -67,13 +67,6 @@ def reevaluate(self): raise NotImplementedError -class FakeCliRepl(cli.CLIRepl, FakeRepl): - def __init__(self): - self.s = "" - self.cpos = 0 - self.rl_history = FakeHistory() - - class TestMatchesIterator(unittest.TestCase): def setUp(self): self.matches = ["bobby", "bobbies", "bobberina"] @@ -539,148 +532,5 @@ def __init__(self, *args, **kwargs): self.assertEqual(self.repl.matches_iter.matches, ["apple2=", "apple="]) -class TestCliRepl(unittest.TestCase): - def setUp(self): - self.repl = FakeCliRepl() - - def test_atbol(self): - self.assertTrue(self.repl.atbol()) - - self.repl.s = "\t\t" - self.assertTrue(self.repl.atbol()) - - self.repl.s = "\t\tnot an empty line" - self.assertFalse(self.repl.atbol()) - - def test_addstr(self): - self.repl.complete = mock.Mock(True) - - self.repl.s = "foo" - self.repl.addstr("bar") - self.assertEqual(self.repl.s, "foobar") - - self.repl.cpos = 3 - self.repl.addstr("buzz") - self.assertEqual(self.repl.s, "foobuzzbar") - - -class TestCliReplTab(unittest.TestCase): - def setUp(self): - self.repl = FakeCliRepl() - - # 3 Types of tab complete - def test_simple_tab_complete(self): - self.repl.matches_iter = MagicIterMock() - self.repl.matches_iter.__bool__.return_value = False - self.repl.complete = mock.Mock() - self.repl.print_line = mock.Mock() - self.repl.matches_iter.is_cseq.return_value = False - self.repl.show_list = mock.Mock() - self.repl.funcprops = mock.Mock() - self.repl.arg_pos = mock.Mock() - self.repl.matches_iter.cur_line.return_value = (None, "foobar") - - self.repl.s = "foo" - self.repl.tab() - self.assertTrue(self.repl.complete.called) - self.repl.complete.assert_called_with(tab=True) - self.assertEqual(self.repl.s, "foobar") - - @unittest.skip("disabled while non-simple completion is disabled") - def test_substring_tab_complete(self): - self.repl.s = "bar" - self.repl.config.autocomplete_mode = ( - autocomplete.AutocompleteModes.FUZZY - ) - self.repl.tab() - self.assertEqual(self.repl.s, "foobar") - self.repl.tab() - self.assertEqual(self.repl.s, "foofoobar") - - @unittest.skip("disabled while non-simple completion is disabled") - def test_fuzzy_tab_complete(self): - self.repl.s = "br" - self.repl.config.autocomplete_mode = ( - autocomplete.AutocompleteModes.FUZZY - ) - self.repl.tab() - self.assertEqual(self.repl.s, "foobar") - - # Edge Cases - def test_normal_tab(self): - """make sure pressing the tab key will - still in some cases add a tab""" - self.repl.s = "" - self.repl.config = mock.Mock() - self.repl.config.tab_length = 4 - self.repl.complete = mock.Mock() - self.repl.print_line = mock.Mock() - self.repl.tab() - self.assertEqual(self.repl.s, " ") - - def test_back_parameter(self): - self.repl.matches_iter = mock.Mock() - self.repl.matches_iter.matches = True - self.repl.matches_iter.previous.return_value = "previtem" - self.repl.matches_iter.is_cseq.return_value = False - self.repl.show_list = mock.Mock() - self.repl.funcprops = mock.Mock() - self.repl.arg_pos = mock.Mock() - self.repl.matches_iter.cur_line.return_value = (None, "previtem") - self.repl.print_line = mock.Mock() - self.repl.s = "foo" - self.repl.cpos = 0 - self.repl.tab(back=True) - self.assertTrue(self.repl.matches_iter.previous.called) - self.assertTrue(self.repl.s, "previtem") - - # Attribute Tests - @unittest.skip("disabled while non-simple completion is disabled") - def test_fuzzy_attribute_tab_complete(self): - """Test fuzzy attribute with no text""" - self.repl.s = "Foo." - self.repl.config.autocomplete_mode = ( - autocomplete.AutocompleteModes.FUZZY - ) - - self.repl.tab() - self.assertEqual(self.repl.s, "Foo.foobar") - - @unittest.skip("disabled while non-simple completion is disabled") - def test_fuzzy_attribute_tab_complete2(self): - """Test fuzzy attribute with some text""" - self.repl.s = "Foo.br" - self.repl.config.autocomplete_mode = ( - autocomplete.AutocompleteModes.FUZZY - ) - - self.repl.tab() - self.assertEqual(self.repl.s, "Foo.foobar") - - # Expand Tests - def test_simple_expand(self): - self.repl.s = "f" - self.cpos = 0 - self.repl.matches_iter = mock.Mock() - self.repl.matches_iter.is_cseq.return_value = True - self.repl.matches_iter.substitute_cseq.return_value = (3, "foo") - self.repl.print_line = mock.Mock() - self.repl.tab() - self.assertEqual(self.repl.s, "foo") - - @unittest.skip("disabled while non-simple completion is disabled") - def test_substring_expand_forward(self): - self.repl.config.autocomplete_mode = ( - autocomplete.AutocompleteModes.SUBSTRING - ) - self.repl.s = "ba" - self.repl.tab() - self.assertEqual(self.repl.s, "bar") - - @unittest.skip("disabled while non-simple completion is disabled") - def test_fuzzy_expand(self): - pass - - if __name__ == "__main__": unittest.main() diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index e11140ed2..c69572910 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -61,42 +61,14 @@ msgstr "" msgid "File to execute and additional arguments passed on to the executed script." msgstr "" -#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 #: bpython/urwid.py:539 msgid "y" msgstr "" -#: bpython/cli.py:320 bpython/urwid.py:539 +#: bpython/urwid.py:539 msgid "yes" msgstr "" -#: bpython/cli.py:1696 -msgid "Rewind" -msgstr "" - -#: bpython/cli.py:1697 -msgid "Save" -msgstr "" - -#: bpython/cli.py:1698 -msgid "Pastebin" -msgstr "" - -#: bpython/cli.py:1699 -msgid "Pager" -msgstr "" - -#: bpython/cli.py:1700 -msgid "Show Source" -msgstr "" - -#: bpython/cli.py:1947 -msgid "" -"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " -"This backend has been deprecated in version 0.19 and might disappear in a" -" future version." -msgstr "" - #: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" diff --git a/bpython/translations/de/LC_MESSAGES/bpython.po b/bpython/translations/de/LC_MESSAGES/bpython.po index 79b3acf71..feb534f7f 100644 --- a/bpython/translations/de/LC_MESSAGES/bpython.po +++ b/bpython/translations/de/LC_MESSAGES/bpython.po @@ -67,45 +67,15 @@ msgstr "" "Auszuführende Datei und zusätzliche Argumente, die an das Script " "übergeben werden sollen." -#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 +#: bpython/curtsiesfrontend/interaction.py:107 #: bpython/urwid.py:539 msgid "y" msgstr "j" -#: bpython/cli.py:320 bpython/urwid.py:539 +#: bpython/urwid.py:539 msgid "yes" msgstr "ja" -#: bpython/cli.py:1696 -msgid "Rewind" -msgstr "Rückgängig" - -#: bpython/cli.py:1697 -msgid "Save" -msgstr "Speichern" - -#: bpython/cli.py:1698 -msgid "Pastebin" -msgstr "Pastebin" - -#: bpython/cli.py:1699 -msgid "Pager" -msgstr "" - -#: bpython/cli.py:1700 -msgid "Show Source" -msgstr "Quellcode anzeigen" - -#: bpython/cli.py:1947 -msgid "" -"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " -"This backend has been deprecated in version 0.19 and might disappear in a" -" future version." -msgstr "" -"ACHTUNG: `bpython-cli` wird verwendet, die curses Implementierung von " -"`bpython`. Diese Implementierung wird ab Version 0.19 nicht mehr aktiv " -"unterstützt und wird in einer zukünftigen Version entfernt werden." - #: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" diff --git a/bpython/translations/es_ES/LC_MESSAGES/bpython.po b/bpython/translations/es_ES/LC_MESSAGES/bpython.po index 5af25b37a..d34872816 100644 --- a/bpython/translations/es_ES/LC_MESSAGES/bpython.po +++ b/bpython/translations/es_ES/LC_MESSAGES/bpython.po @@ -62,42 +62,15 @@ msgstr "" msgid "File to execute and additional arguments passed on to the executed script." msgstr "" -#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 +#: bpython/curtsiesfrontend/interaction.py:107 #: bpython/urwid.py:539 msgid "y" msgstr "s" -#: bpython/cli.py:320 bpython/urwid.py:539 +#: bpython/urwid.py:539 msgid "yes" msgstr "si" -#: bpython/cli.py:1696 -msgid "Rewind" -msgstr "" - -#: bpython/cli.py:1697 -msgid "Save" -msgstr "" - -#: bpython/cli.py:1698 -msgid "Pastebin" -msgstr "" - -#: bpython/cli.py:1699 -msgid "Pager" -msgstr "" - -#: bpython/cli.py:1700 -msgid "Show Source" -msgstr "" - -#: bpython/cli.py:1947 -msgid "" -"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " -"This backend has been deprecated in version 0.19 and might disappear in a" -" future version." -msgstr "" - #: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" diff --git a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po index 32bbec662..ba1205048 100644 --- a/bpython/translations/fr_FR/LC_MESSAGES/bpython.po +++ b/bpython/translations/fr_FR/LC_MESSAGES/bpython.po @@ -66,42 +66,15 @@ msgstr "" msgid "File to execute and additional arguments passed on to the executed script." msgstr "" -#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 +#: bpython/curtsiesfrontend/interaction.py:107 #: bpython/urwid.py:539 msgid "y" msgstr "o" -#: bpython/cli.py:320 bpython/urwid.py:539 +#: bpython/urwid.py:539 msgid "yes" msgstr "oui" -#: bpython/cli.py:1696 -msgid "Rewind" -msgstr "Rembobiner" - -#: bpython/cli.py:1697 -msgid "Save" -msgstr "Sauvegarder" - -#: bpython/cli.py:1698 -msgid "Pastebin" -msgstr "" - -#: bpython/cli.py:1699 -msgid "Pager" -msgstr "" - -#: bpython/cli.py:1700 -msgid "Show Source" -msgstr "Montrer le code source" - -#: bpython/cli.py:1947 -msgid "" -"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " -"This backend has been deprecated in version 0.19 and might disappear in a" -" future version." -msgstr "" - #: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" diff --git a/bpython/translations/it_IT/LC_MESSAGES/bpython.po b/bpython/translations/it_IT/LC_MESSAGES/bpython.po index d0076cffd..46488bc3c 100644 --- a/bpython/translations/it_IT/LC_MESSAGES/bpython.po +++ b/bpython/translations/it_IT/LC_MESSAGES/bpython.po @@ -62,42 +62,15 @@ msgstr "" msgid "File to execute and additional arguments passed on to the executed script." msgstr "" -#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 +#: bpython/curtsiesfrontend/interaction.py:107 #: bpython/urwid.py:539 msgid "y" msgstr "s" -#: bpython/cli.py:320 bpython/urwid.py:539 +#: bpython/urwid.py:539 msgid "yes" msgstr "si" -#: bpython/cli.py:1696 -msgid "Rewind" -msgstr "" - -#: bpython/cli.py:1697 -msgid "Save" -msgstr "" - -#: bpython/cli.py:1698 -msgid "Pastebin" -msgstr "" - -#: bpython/cli.py:1699 -msgid "Pager" -msgstr "" - -#: bpython/cli.py:1700 -msgid "Show Source" -msgstr "" - -#: bpython/cli.py:1947 -msgid "" -"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " -"This backend has been deprecated in version 0.19 and might disappear in a" -" future version." -msgstr "" - #: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" diff --git a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po index d110f3ba8..375f4f32e 100644 --- a/bpython/translations/nl_NL/LC_MESSAGES/bpython.po +++ b/bpython/translations/nl_NL/LC_MESSAGES/bpython.po @@ -62,42 +62,15 @@ msgstr "" msgid "File to execute and additional arguments passed on to the executed script." msgstr "" -#: bpython/cli.py:320 bpython/curtsiesfrontend/interaction.py:107 +#: bpython/curtsiesfrontend/interaction.py:107 #: bpython/urwid.py:539 msgid "y" msgstr "j" -#: bpython/cli.py:320 bpython/urwid.py:539 +#: bpython/urwid.py:539 msgid "yes" msgstr "ja" -#: bpython/cli.py:1696 -msgid "Rewind" -msgstr "" - -#: bpython/cli.py:1697 -msgid "Save" -msgstr "" - -#: bpython/cli.py:1698 -msgid "Pastebin" -msgstr "" - -#: bpython/cli.py:1699 -msgid "Pager" -msgstr "" - -#: bpython/cli.py:1700 -msgid "Show Source" -msgstr "" - -#: bpython/cli.py:1947 -msgid "" -"WARNING: You are using `bpython-cli`, the curses backend for `bpython`. " -"This backend has been deprecated in version 0.19 and might disappear in a" -" future version." -msgstr "" - #: bpython/curtsies.py:201 msgid "start by pasting lines of a file into session" msgstr "" diff --git a/doc/sphinx/source/tips.rst b/doc/sphinx/source/tips.rst index f2519b405..4bfbc2e43 100644 --- a/doc/sphinx/source/tips.rst +++ b/doc/sphinx/source/tips.rst @@ -16,7 +16,7 @@ equivalent file. .. code-block:: bash - alias bpython3.5='PYTHONPATH=~/python/bpython python3.5 -m bpython.cli' + alias bpython3.5='PYTHONPATH=~/python/bpython python3.5 -m bpython.curtsies' Where the `~/python/bpython`-path is the path to where your bpython source code resides. diff --git a/doc/sphinx/source/windows.rst b/doc/sphinx/source/windows.rst index 6d2c05a0b..5374f70fb 100644 --- a/doc/sphinx/source/windows.rst +++ b/doc/sphinx/source/windows.rst @@ -7,9 +7,3 @@ other platforms as well. There are no official binaries for bpython on Windows (though this is something we plan on providing in the future). - -The easiest way to get `bpython.cli` (the curses frontend running) is to install -an unofficial windows binary for pdcurses from: -http://www.lfd.uci.edu/~gohlke/pythonlibs/#curses. After this you can just -`pip install bpython` and run bpython curses frontend like you would on a Linux -system (e.g. by typing `bpython-curses` on your prompt). diff --git a/setup.cfg b/setup.cfg index 8c4294d9c..c0c5d03e7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,7 +38,6 @@ watch = watchdog [options.entry_points] console_scripts = bpython = bpython.curtsies:main - bpython-curses = bpython.cli:main bpython-urwid = bpython.urwid:main [urwid] bpdb = bpdb:main From 5aa1989a8ed299aac155b24c4a4b4adf0542cc17 Mon Sep 17 00:00:00 2001 From: supakeen Date: Tue, 9 Nov 2021 17:46:04 +0000 Subject: [PATCH 1563/1650] Refer directly to the top-level package in docs. --- doc/sphinx/source/tips.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/tips.rst b/doc/sphinx/source/tips.rst index 4bfbc2e43..0745e3bfa 100644 --- a/doc/sphinx/source/tips.rst +++ b/doc/sphinx/source/tips.rst @@ -16,7 +16,7 @@ equivalent file. .. code-block:: bash - alias bpython3.5='PYTHONPATH=~/python/bpython python3.5 -m bpython.curtsies' + alias bpython3.5='PYTHONPATH=~/python/bpython python3.5 -m bpython' Where the `~/python/bpython`-path is the path to where your bpython source code resides. From acffa531199c090baa2d512851b1cedd432fefe5 Mon Sep 17 00:00:00 2001 From: supakeen Date: Tue, 9 Nov 2021 17:47:08 +0000 Subject: [PATCH 1564/1650] Bit too heavy handed on the translations. --- bpython/translations/bpython.pot | 1 + 1 file changed, 1 insertion(+) diff --git a/bpython/translations/bpython.pot b/bpython/translations/bpython.pot index c69572910..9237869da 100644 --- a/bpython/translations/bpython.pot +++ b/bpython/translations/bpython.pot @@ -61,6 +61,7 @@ msgstr "" msgid "File to execute and additional arguments passed on to the executed script." msgstr "" +#: bpython/curtsiesfrontend/interaction.py:107 #: bpython/urwid.py:539 msgid "y" msgstr "" From 26fc2b580c774c824d1f234b32a3be0f404db6be Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 1 May 2023 10:33:26 +0200 Subject: [PATCH 1565/1650] Require Sphinx < 7 for now Removal of the setuptools integration from Sphinx breaks our builds. See https://github.com/sphinx-doc/sphinx/pull/11363 for the change in Sphinx. --- .github/workflows/build.yaml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1e2a374a5..9254d49cd 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -32,7 +32,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" + pip install urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5,<7" pip install pytest pytest-cov numpy - name: Build with Python ${{ matrix.python-version }} run: | diff --git a/setup.py b/setup.py index 6790b9d78..7ca279d35 100755 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ from sphinx.setup_command import BuildDoc # Sphinx 1.5 and newer support Python 3.6 - using_sphinx = sphinx.__version__ >= "1.5" + using_sphinx = sphinx.__version__ >= "1.5" and sphinx.__version__ < "7.0" except ImportError: using_sphinx = False From db6a559724a59713ac905d7b0533ef382fab930d Mon Sep 17 00:00:00 2001 From: Jochen Kupperschmidt Date: Tue, 11 Jul 2023 23:24:07 +0200 Subject: [PATCH 1566/1650] Explicitly set README content type The README, being a reStructuredText document, is not rendered as such on PyPI, just as plaintext. This *should* fix that. --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index c0c5d03e7..1fe4a6f99 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,7 @@ [metadata] name = bpython long_description = file: README.rst +long_description_content_type = text/x-rst license = MIT license_files = LICENSE author = Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al. From d58e392f188118b3f6d3c964041492dbb2cd3694 Mon Sep 17 00:00:00 2001 From: gpotter2 <10530980+gpotter2@users.noreply.github.com> Date: Sun, 9 Jul 2023 16:52:27 +0200 Subject: [PATCH 1567/1650] Fix __signature__ support if object has a __file__ --- bpython/inspection.py | 10 +++++++--- bpython/test/test_repl.py | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index fe1e3a0a2..2b734cdfb 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -289,9 +289,13 @@ def getfuncprops(func: str, f: Callable) -> Optional[FuncProps]: return None try: argspec = _get_argspec_from_signature(f) - fprops = FuncProps( - func, _fix_default_values(f, argspec), is_bound_method - ) + try: + argspec = _fix_default_values(f, argspec) + except KeyError as ex: + # Parsing of the source failed. If f has a __signature__, we trust it. + if not hasattr(f, "__signature__"): + raise ex + fprops = FuncProps(func, argspec, is_bound_method) except (TypeError, KeyError, ValueError): argspec_pydoc = _getpydocspec(f) if argspec_pydoc is None: diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 74f4b7217..8c3b85cc8 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -1,5 +1,6 @@ import collections import inspect +import os import socket import sys import tempfile @@ -523,13 +524,19 @@ def __init__(self, *args, **kwargs): inspect.Parameter("pinetree", inspect.Parameter.KEYWORD_ONLY), ]) """ - for line in code.split("\n"): - print(line[8:]) - self.repl.push(line[8:]) + code = [x[8:] for x in code.split("\n")] + for line in code: + self.repl.push(line) - self.assertTrue(self.repl.complete()) - self.assertTrue(hasattr(self.repl.matches_iter, "matches")) - self.assertEqual(self.repl.matches_iter.matches, ["apple2=", "apple="]) + with mock.patch( + "bpython.inspection.inspect.getsourcelines", + return_value=(code, None), + ): + self.assertTrue(self.repl.complete()) + self.assertTrue(hasattr(self.repl.matches_iter, "matches")) + self.assertEqual( + self.repl.matches_iter.matches, ["apple2=", "apple="] + ) if __name__ == "__main__": From 7bf93f510752e36cb72fdfd17d7db46b48e438b9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 20 Jul 2023 22:44:42 +0200 Subject: [PATCH 1568/1650] Fix handling of SystemExit arguments (fixes #995) --- bpython/curtsiesfrontend/coderunner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/curtsiesfrontend/coderunner.py b/bpython/curtsiesfrontend/coderunner.py index cddf1169d..f059fab88 100644 --- a/bpython/curtsiesfrontend/coderunner.py +++ b/bpython/curtsiesfrontend/coderunner.py @@ -52,7 +52,7 @@ class Unfinished(RequestFromCodeRunner): class SystemExitRequest(RequestFromCodeRunner): """Running code raised a SystemExit""" - def __init__(self, args): + def __init__(self, *args): self.args = args From db6c9bdf6d9a1d128f04ffd053591b2e1289b08a Mon Sep 17 00:00:00 2001 From: gpotter2 <10530980+gpotter2@users.noreply.github.com> Date: Sat, 22 Jul 2023 16:48:00 +0200 Subject: [PATCH 1569/1650] Complete parameters without input --- bpython/autocomplete.py | 7 ++++++- bpython/test/test_autocomplete.py | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index e0849c6d2..10f039d22 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -604,7 +604,12 @@ def matches( return matches if matches else None def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: - return lineparts.current_word(cursor_offset, line) + r = lineparts.current_word(cursor_offset, line) + if r and r.word[-1] == "(": + # if the word ends with a (, it's the parent word with an empty + # param. Return an empty word + return lineparts.LinePart(r.stop, r.stop, "") + return r class ExpressionAttributeCompletion(AttrCompletion): diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 0000b0b60..2bbd90b33 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -435,3 +435,7 @@ def func(apple, apricot, banana, carrot): self.assertSetEqual( com.matches(3, "car", funcprops=funcspec), {"carrot="} ) + self.assertSetEqual( + com.matches(5, "func(", funcprops=funcspec), + {"apple=", "apricot=", "banana=", "carrot="}, + ) From de333118a7dc89925c7e3d1f246271b814a4e37c Mon Sep 17 00:00:00 2001 From: gpotter2 <10530980+gpotter2@users.noreply.github.com> Date: Sat, 22 Jul 2023 16:49:55 +0200 Subject: [PATCH 1570/1650] Better completion results order --- bpython/autocomplete.py | 14 +++++++++++++- bpython/test/test_autocomplete.py | 9 +++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 10f039d22..737599923 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -747,6 +747,16 @@ def get_completer( double underscore methods like __len__ in method signatures """ + def _cmpl_sort(x: str) -> Tuple[Any, ...]: + """ + Function used to sort the matches. + """ + # put parameters above everything in completion + return ( + x[-1] != "=", + x, + ) + for completer in completers: try: matches = completer.matches( @@ -765,7 +775,9 @@ def get_completer( ) continue if matches is not None: - return sorted(matches), (completer if matches else None) + return sorted(matches, key=_cmpl_sort), ( + completer if matches else None + ) return [], None diff --git a/bpython/test/test_autocomplete.py b/bpython/test/test_autocomplete.py index 2bbd90b33..da32fbb8c 100644 --- a/bpython/test/test_autocomplete.py +++ b/bpython/test/test_autocomplete.py @@ -106,6 +106,15 @@ def test_two_completers_get_both(self): cumulative = autocomplete.CumulativeCompleter([a, b]) self.assertEqual(cumulative.matches(3, "abc"), {"a", "b"}) + def test_order_completer(self): + a = self.completer(["ax", "ab="]) + b = self.completer(["aa"]) + cumulative = autocomplete.CumulativeCompleter([a, b]) + self.assertEqual( + autocomplete.get_completer([cumulative], 1, "a"), + (["ab=", "aa", "ax"], cumulative), + ) + class TestFilenameCompletion(unittest.TestCase): def setUp(self): From 77bed91c9ed6b242f1efeb68047ffb43172133d3 Mon Sep 17 00:00:00 2001 From: gpotter2 <10530980+gpotter2@users.noreply.github.com> Date: Wed, 26 Jul 2023 09:31:46 +0200 Subject: [PATCH 1571/1650] Apply suggestion --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 737599923..a36c7beb4 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -747,7 +747,7 @@ def get_completer( double underscore methods like __len__ in method signatures """ - def _cmpl_sort(x: str) -> Tuple[Any, ...]: + def _cmpl_sort(x: str) -> Tuple[bool, ...]: """ Function used to sort the matches. """ From be21521902cda73cde331480b99c5709d757a9ae Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 13 Jul 2023 14:17:34 +0200 Subject: [PATCH 1572/1650] Fix argument description --- bpython/args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/args.py b/bpython/args.py index b9e68e1d8..d1037e1ca 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -131,7 +131,7 @@ def callback(group): "--quiet", "-q", action="store_true", - help=_("Don't flush the output to stdout."), + help=_("Don't print version banner."), ) parser.add_argument( "--version", From 1db0436babf5c964a954f875e481bc9dc686d104 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 26 Jul 2023 09:46:46 +0200 Subject: [PATCH 1573/1650] Fix type annotation --- bpython/autocomplete.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index a36c7beb4..000fbde9f 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -747,7 +747,7 @@ def get_completer( double underscore methods like __len__ in method signatures """ - def _cmpl_sort(x: str) -> Tuple[bool, ...]: + def _cmpl_sort(x: str) -> Tuple[bool, str]: """ Function used to sort the matches. """ From 590507b57f269ae33cfaadc2cafa1685f0c538e7 Mon Sep 17 00:00:00 2001 From: Mikolaj Klikowicz Date: Fri, 11 Aug 2023 13:02:07 +0200 Subject: [PATCH 1574/1650] Handle AttributeError Signed-off-by: Mikolaj Klikowicz --- bpython/inspection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/inspection.py b/bpython/inspection.py index 2b734cdfb..e97a272bc 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -104,13 +104,13 @@ def __enter__(self) -> None: if __getattr__ is not None: try: setattr(type_, "__getattr__", (lambda *_, **__: None)) - except TypeError: + except (TypeError, AttributeError): __getattr__ = None __getattribute__ = getattr(type_, "__getattribute__", None) if __getattribute__ is not None: try: setattr(type_, "__getattribute__", object.__getattribute__) - except TypeError: + except (TypeError, AttributeError): # XXX: This happens for e.g. built-in types __getattribute__ = None self._attribs = (__getattribute__, __getattr__) From f64677ffd9ed0446506c84e6d76e9d7272cc06ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 04:47:50 +0000 Subject: [PATCH 1575/1650] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 2 +- .github/workflows/lint.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9254d49cd..a55ee36c3 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,7 +21,7 @@ jobs: - "3.11" - "pypy-3.8" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index f644f5434..d02dd2dfb 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -8,7 +8,7 @@ jobs: black: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 - name: Install dependencies @@ -21,7 +21,7 @@ jobs: codespell: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: codespell-project/actions-codespell@master with: skip: '*.po' @@ -30,7 +30,7 @@ jobs: mypy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 - name: Install dependencies From 7c9e8513c01b7ea77b44d5c5a8e94dcf15ccb8a7 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 4 Oct 2023 10:45:08 +0200 Subject: [PATCH 1576/1650] CI: test with Python 3.12 --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a55ee36c3..ffb1c2ae0 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -19,6 +19,7 @@ jobs: - "3.9" - "3.10" - "3.11" + - "3.12" - "pypy-3.8" steps: - uses: actions/checkout@v4 From cafa87c947d6e25dadd61dfa1175df953bf5445d Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 11 Oct 2023 18:25:48 +0200 Subject: [PATCH 1577/1650] Do not fail if modules don't have __version__ (fixes #1001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … even if they should. --- bpython/args.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index d1037e1ca..51cc3d25c 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -37,6 +37,7 @@ import sys from pathlib import Path from typing import Tuple, List, Optional, NoReturn, Callable +from types import ModuleType from . import __version__, __copyright__ from .config import default_config_path, Config @@ -67,6 +68,13 @@ def copyright_banner() -> str: return _("{} See AUTHORS.rst for details.").format(__copyright__) +def log_version(module: ModuleType, name: str) -> None: + try: + logger.info("%s: %s", name, module.__version__) # type: ignore + except AttributeError: + logger.info("%s: unknown version", name) + + Options = Tuple[str, str, Callable[[argparse._ArgumentGroup], None]] @@ -211,27 +219,27 @@ def callback(group): try: import curtsies - logger.info("curtsies: %s", curtsies.__version__) + log_version(curtsies, "curtsies") except ImportError: # may happen on Windows logger.info("curtsies: not available") - logger.info("cwcwidth: %s", cwcwidth.__version__) - logger.info("greenlet: %s", greenlet.__version__) - logger.info("pygments: %s", pygments.__version__) # type: ignore - logger.info("pyxdg: %s", xdg.__version__) # type: ignore - logger.info("requests: %s", requests.__version__) + log_version(cwcwidth, "cwcwidth") + log_version(greenlet, "greenlet") + log_version(pygments, "pygments") + log_version(xdg, "pyxdg") + log_version(requests, "requests") # versions of optional dependencies try: import pyperclip - logger.info("pyperclip: %s", pyperclip.__version__) # type: ignore + log_version(pyperclip, "pyperclip") except ImportError: logger.info("pyperclip: not available") try: import jedi - logger.info("jedi: %s", jedi.__version__) + log_version(jedi, "jedi") except ImportError: logger.info("jedi: not available") try: From 3a440274be26338e873e3e6144e9e20116ef1d4a Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 11 Oct 2023 18:42:18 +0200 Subject: [PATCH 1578/1650] Avoid the use of Exceptions for logic --- bpython/args.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 51cc3d25c..ed0b0055b 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -69,10 +69,7 @@ def copyright_banner() -> str: def log_version(module: ModuleType, name: str) -> None: - try: - logger.info("%s: %s", name, module.__version__) # type: ignore - except AttributeError: - logger.info("%s: unknown version", name) + logger.info("%s: %s", name, module.__version__ if hasattr(module, "__version__") else "unknown version") # type: ignore Options = Tuple[str, str, Callable[[argparse._ArgumentGroup], None]] From 3fda3af0df1a92c5794c6b396501c97647853222 Mon Sep 17 00:00:00 2001 From: Joan Lucas Date: Mon, 27 Nov 2023 20:59:26 -0300 Subject: [PATCH 1579/1650] Added functions return typing in some files: __init__.py; keys.py and lazyre.py --- bpython/__init__.py | 3 ++- bpython/keys.py | 4 ++-- bpython/lazyre.py | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bpython/__init__.py b/bpython/__init__.py index dff06c0fa..8c7bfb37a 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -21,6 +21,7 @@ # THE SOFTWARE. import os.path +from typing import Any try: from ._version import __version__ as version # type: ignore @@ -36,7 +37,7 @@ package_dir = os.path.abspath(os.path.dirname(__file__)) -def embed(locals_=None, args=None, banner=None): +def embed(locals_=None, args=None, banner=None) -> Any: if args is None: args = ["-i", "-q"] diff --git a/bpython/keys.py b/bpython/keys.py index cfcac86be..fe27dbcc4 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -42,10 +42,10 @@ def __getitem__(self, key: str) -> T: f"Configured keymap ({key}) does not exist in bpython.keys" ) - def __delitem__(self, key: str): + def __delitem__(self, key: str) -> None: del self.map[key] - def __setitem__(self, key: str, value: T): + def __setitem__(self, key: str, value: T) -> None: self.map[key] = value diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 0ca5b9ffa..8d166b74f 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -21,7 +21,7 @@ # THE SOFTWARE. import re -from typing import Optional, Pattern, Match, Optional +from typing import Optional, Pattern, Match, Optional, Iterator try: from functools import cached_property @@ -43,7 +43,7 @@ def __init__(self, regex: str, flags: int = 0) -> None: def compiled(self) -> Pattern[str]: return re.compile(self.regex, self.flags) - def finditer(self, *args, **kwargs): + def finditer(self, *args, **kwargs) -> Iterator[Match[str]]: return self.compiled.finditer(*args, **kwargs) def search(self, *args, **kwargs) -> Optional[Match[str]]: From f200dac951cee8c8d0f15abd2570eb13e58f729b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 07:38:27 +0000 Subject: [PATCH 1580/1650] Bump actions/setup-python from 4 to 5 (#1004) --- .github/workflows/build.yaml | 2 +- .github/workflows/lint.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ffb1c2ae0..864c03cc8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -26,7 +26,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d02dd2dfb..d960c6d89 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -32,7 +32,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 - name: Install dependencies run: | python -m pip install --upgrade pip From ca13bf5727d91d3208e84297c2829aa8474b4620 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 08:56:25 +0100 Subject: [PATCH 1581/1650] Bump codecov/codecov-action from 3 to 4 (#1006) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 864c03cc8..37128f249 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -46,7 +46,7 @@ jobs: run: | pytest --cov=bpython --cov-report=xml -v - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 env: PYTHON_VERSION: ${{ matrix.python-version }} with: From 0f238b7590037c85d7b19059ee8b41cd1d1f08f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Mon, 29 Apr 2024 13:30:34 +0200 Subject: [PATCH 1582/1650] Fix simplerepl demo: missing arg for BaseRepl init (#1017) Default value was dropped in 8d16a71ef404db66d2c6fae6c362640da8ae240d --- doc/sphinx/source/simplerepl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sphinx/source/simplerepl.py b/doc/sphinx/source/simplerepl.py index 8a8dda74e..8496f0dd6 100644 --- a/doc/sphinx/source/simplerepl.py +++ b/doc/sphinx/source/simplerepl.py @@ -42,7 +42,7 @@ class SimpleRepl(BaseRepl): def __init__(self, config): self.requested_events = [] - BaseRepl.__init__(self, config) + BaseRepl.__init__(self, config, window=None) def _request_refresh(self): self.requested_events.append(bpythonevents.RefreshRequestEvent()) From 925b733e5e456daf0516f3ff9ac3877e689fd436 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Thu, 25 Apr 2024 13:57:35 +0100 Subject: [PATCH 1583/1650] Import build from setuptools --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7ca279d35..021940883 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ import subprocess from setuptools import setup -from distutils.command.build import build +from setuptools.command.build import build try: from babel.messages import frontend as babel From ded2d7fe3fca3df64de1938c1dcf7638ec900227 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Thu, 25 Apr 2024 13:58:16 +0100 Subject: [PATCH 1584/1650] Avoid patching the original build class attribute --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 021940883..0cceb9406 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ import subprocess from setuptools import setup -from setuptools.command.build import build +from setuptools.command.build import build as _orig_build try: from babel.messages import frontend as babel @@ -122,6 +122,11 @@ def git_describe_to_python_version(version): vf.write(f'__version__ = "{version}"\n') +class build(_orig_build): + # Avoid patching the original class' attribute (more robust customisation) + sub_commands = _orig_build.sub_commands[:] + + cmdclass = {"build": build} translations_dir = os.path.join("bpython", "translations") From ac7c11ad850a8ca649a48f26eef0b2c59f204f3e Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Thu, 25 Apr 2024 14:13:23 +0100 Subject: [PATCH 1585/1650] Bump setuptools version Reliably import `setuptools.command.build`. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b7bd3196a..924722b0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] requires = [ - "setuptools >= 43", + "setuptools >= 62.4.0", ] build-backend = "setuptools.build_meta" From a9b1324ad535774727545896ec54515e527423ab Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Tue, 30 Apr 2024 14:49:13 +0100 Subject: [PATCH 1586/1650] Bump setuptools in requirement.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4c750a694..cc8fbff84 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ cwcwidth greenlet pyxdg requests -setuptools \ No newline at end of file +setuptools>=62.4.0 From d54061317d767c64eb2d466fa14ca56bef5bb9eb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 1 Jun 2024 21:47:54 +0200 Subject: [PATCH 1587/1650] Apply black --- bpython/curtsies.py | 15 ++++++--------- bpython/curtsiesfrontend/repl.py | 8 +++++--- bpython/curtsiesfrontend/replpainter.py | 8 +++++--- bpython/paste.py | 3 +-- bpython/test/test_preprocess.py | 4 ++-- bpython/urwid.py | 1 - 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 6dc8d1f78..11b960503 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -40,14 +40,11 @@ class SupportsEventGeneration(Protocol): def send( self, timeout: Optional[float] - ) -> Union[str, curtsies.events.Event, None]: - ... + ) -> Union[str, curtsies.events.Event, None]: ... - def __iter__(self) -> "SupportsEventGeneration": - ... + def __iter__(self) -> "SupportsEventGeneration": ... - def __next__(self) -> Union[str, curtsies.events.Event, None]: - ... + def __next__(self) -> Union[str, curtsies.events.Event, None]: ... class FullCurtsiesRepl(BaseRepl): @@ -69,9 +66,9 @@ def __init__( extra_bytes_callback=self.input_generator.unget_bytes, ) - self._request_refresh_callback: Callable[ - [], None - ] = self.input_generator.event_trigger(events.RefreshRequestEvent) + self._request_refresh_callback: Callable[[], None] = ( + self.input_generator.event_trigger(events.RefreshRequestEvent) + ) self._schedule_refresh_callback = ( self.input_generator.scheduled_event_trigger( events.ScheduledRefreshRequestEvent diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index e4819e193..302e67d4c 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1801,9 +1801,11 @@ def move_screen_up(current_line_start_row): self.current_match, self.docstring, self.config, - self.matches_iter.completer.format - if self.matches_iter.completer - else None, + ( + self.matches_iter.completer.format + if self.matches_iter.completer + else None + ), ) if ( diff --git a/bpython/curtsiesfrontend/replpainter.py b/bpython/curtsiesfrontend/replpainter.py index 00675451d..3b63ca4c9 100644 --- a/bpython/curtsiesfrontend/replpainter.py +++ b/bpython/curtsiesfrontend/replpainter.py @@ -74,9 +74,11 @@ def matches_lines(rows, columns, matches, current, config, match_format): result = [ fmtstr(" ").join( - color(m.ljust(max_match_width)) - if m != current - else highlight_color(m.ljust(max_match_width)) + ( + color(m.ljust(max_match_width)) + if m != current + else highlight_color(m.ljust(max_match_width)) + ) for m in matches[i : i + words_wide] ) for i in range(0, len(matches), words_wide) diff --git a/bpython/paste.py b/bpython/paste.py index fd140a0ec..a81c0c6c9 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -37,8 +37,7 @@ class PasteFailed(Exception): class Paster(Protocol): - def paste(self, s: str) -> Tuple[str, Optional[str]]: - ... + def paste(self, s: str) -> Tuple[str, Optional[str]]: ... class PastePinnwand: diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index ee3f20857..e9309f1e1 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -16,10 +16,10 @@ def get_fodder_source(test_name): pattern = rf"#StartTest-{test_name}\n(.*?)#EndTest" - orig, xformed = [ + orig, xformed = ( re.search(pattern, inspect.getsource(module), re.DOTALL) for module in [original, processed] - ] + ) if not orig: raise ValueError( diff --git a/bpython/urwid.py b/bpython/urwid.py index 4b41c12aa..9b061340f 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -99,7 +99,6 @@ def buildProtocol(self, addr): if urwid.VERSION < (1, 0, 0) and hasattr(urwid, "TwistedEventLoop"): class TwistedEventLoop(urwid.TwistedEventLoop): - """TwistedEventLoop modified to properly stop the reactor. urwid 0.9.9 and 0.9.9.1 crash the reactor on ExitMainLoop instead From ff05288d819fb40a0a2606010abcc6c9d29d6687 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 1 Jun 2024 23:17:19 +0200 Subject: [PATCH 1588/1650] Import BuildDoc from sphinx (fixes #987) --- LICENSE | 28 ++++++++ setup.py | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 229 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 72d02ff63..46f642f27 100644 --- a/LICENSE +++ b/LICENSE @@ -72,3 +72,31 @@ products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. + + +BuildDoc in setup.py is licensed under the BSD-2 license: + +Copyright 2007-2021 Sebastian Wiesner + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/setup.py b/setup.py index 0cceb9406..e158f1a0d 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ import re import subprocess -from setuptools import setup +from setuptools import setup, Command from setuptools.command.build import build as _orig_build try: @@ -17,14 +17,212 @@ try: import sphinx - from sphinx.setup_command import BuildDoc # Sphinx 1.5 and newer support Python 3.6 - using_sphinx = sphinx.__version__ >= "1.5" and sphinx.__version__ < "7.0" + using_sphinx = sphinx.__version__ >= "1.5" except ImportError: using_sphinx = False +if using_sphinx: + import sys + from io import StringIO + + from setuptools.errors import ExecError + from sphinx.application import Sphinx + from sphinx.cmd.build import handle_exception + from sphinx.util.console import color_terminal, nocolor + from sphinx.util.docutils import docutils_namespace, patch_docutils + from sphinx.util.osutil import abspath + + class BuildDoc(Command): + """ + Distutils command to build Sphinx documentation. + The Sphinx build can then be triggered from distutils, and some Sphinx + options can be set in ``setup.py`` or ``setup.cfg`` instead of Sphinx's + own configuration file. + For instance, from `setup.py`:: + # this is only necessary when not using setuptools/distribute + from sphinx.setup_command import BuildDoc + cmdclass = {'build_sphinx': BuildDoc} + name = 'My project' + version = '1.2' + release = '1.2.0' + setup( + name=name, + author='Bernard Montgomery', + version=release, + cmdclass=cmdclass, + # these are optional and override conf.py settings + command_options={ + 'build_sphinx': { + 'project': ('setup.py', name), + 'version': ('setup.py', version), + 'release': ('setup.py', release)}}, + ) + Or add this section in ``setup.cfg``:: + [build_sphinx] + project = 'My project' + version = 1.2 + release = 1.2.0 + """ + + description = "Build Sphinx documentation" + user_options = [ + ("fresh-env", "E", "discard saved environment"), + ("all-files", "a", "build all files"), + ("source-dir=", "s", "Source directory"), + ("build-dir=", None, "Build directory"), + ("config-dir=", "c", "Location of the configuration directory"), + ( + "builder=", + "b", + "The builder (or builders) to use. Can be a comma- " + 'or space-separated list. Defaults to "html"', + ), + ("warning-is-error", "W", "Turn warning into errors"), + ("project=", None, "The documented project's name"), + ("version=", None, "The short X.Y version"), + ( + "release=", + None, + "The full version, including alpha/beta/rc tags", + ), + ( + "today=", + None, + "How to format the current date, used as the " + "replacement for |today|", + ), + ("link-index", "i", "Link index.html to the master doc"), + ("copyright", None, "The copyright string"), + ("pdb", None, "Start pdb on exception"), + ("verbosity", "v", "increase verbosity (can be repeated)"), + ( + "nitpicky", + "n", + "nit-picky mode, warn about all missing references", + ), + ("keep-going", None, "With -W, keep going when getting warnings"), + ] + boolean_options = [ + "fresh-env", + "all-files", + "warning-is-error", + "link-index", + "nitpicky", + ] + + def initialize_options(self) -> None: + self.fresh_env = self.all_files = False + self.pdb = False + self.source_dir: str = None + self.build_dir: str = None + self.builder = "html" + self.warning_is_error = False + self.project = "" + self.version = "" + self.release = "" + self.today = "" + self.config_dir: str = None + self.link_index = False + self.copyright = "" + # Link verbosity to distutils' (which uses 1 by default). + self.verbosity = self.distribution.verbose - 1 # type: ignore + self.traceback = False + self.nitpicky = False + self.keep_going = False + + def _guess_source_dir(self) -> str: + for guess in ("doc", "docs"): + if not os.path.isdir(guess): + continue + for root, dirnames, filenames in os.walk(guess): + if "conf.py" in filenames: + return root + return os.curdir + + def finalize_options(self) -> None: + self.ensure_string_list("builder") + + if self.source_dir is None: + self.source_dir = self._guess_source_dir() + self.announce("Using source directory %s" % self.source_dir) + + self.ensure_dirname("source_dir") + + if self.config_dir is None: + self.config_dir = self.source_dir + + if self.build_dir is None: + build = self.get_finalized_command("build") + self.build_dir = os.path.join(abspath(build.build_base), "sphinx") # type: ignore + + self.doctree_dir = os.path.join(self.build_dir, "doctrees") + + self.builder_target_dirs = [ + (builder, os.path.join(self.build_dir, builder)) + for builder in self.builder + ] + + def run(self) -> None: + if not color_terminal(): + nocolor() + if not self.verbose: # type: ignore + status_stream = StringIO() + else: + status_stream = sys.stdout # type: ignore + confoverrides = {} + if self.project: + confoverrides["project"] = self.project + if self.version: + confoverrides["version"] = self.version + if self.release: + confoverrides["release"] = self.release + if self.today: + confoverrides["today"] = self.today + if self.copyright: + confoverrides["copyright"] = self.copyright + if self.nitpicky: + confoverrides["nitpicky"] = self.nitpicky + + for builder, builder_target_dir in self.builder_target_dirs: + app = None + + try: + confdir = self.config_dir or self.source_dir + with patch_docutils(confdir), docutils_namespace(): + app = Sphinx( + self.source_dir, + self.config_dir, + builder_target_dir, + self.doctree_dir, + builder, + confoverrides, + status_stream, + freshenv=self.fresh_env, + warningiserror=self.warning_is_error, + verbosity=self.verbosity, + keep_going=self.keep_going, + ) + app.build(force_all=self.all_files) + if app.statuscode: + raise ExecError( + "caused by %s builder." % app.builder.name + ) + except Exception as exc: + handle_exception(app, self, exc, sys.stderr) + if not self.pdb: + raise SystemExit(1) from exc + + if not self.link_index: + continue + + src = app.config.root_doc + app.builder.out_suffix # type: ignore + dst = app.builder.get_outfilename("index") # type: ignore + os.symlink(src, dst) + + # version handling From 1f2f6f5b04b52ea939d6bd64e2e8eee83ae917f4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 1 Jun 2024 23:17:39 +0200 Subject: [PATCH 1589/1650] Refactor build command overrides --- setup.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index e158f1a0d..9e24203fd 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ import subprocess from setuptools import setup, Command -from setuptools.command.build import build as _orig_build +from setuptools.command.build import build try: from babel.messages import frontend as babel @@ -320,26 +320,27 @@ def git_describe_to_python_version(version): vf.write(f'__version__ = "{version}"\n') -class build(_orig_build): - # Avoid patching the original class' attribute (more robust customisation) - sub_commands = _orig_build.sub_commands[:] +class custom_build(build): + def run(self): + if using_translations: + self.run_command("compile_catalog") + if using_sphinx: + self.run_command("build_sphinx_man") -cmdclass = {"build": build} +cmdclass = {"build": custom_build} + translations_dir = os.path.join("bpython", "translations") # localization options if using_translations: - build.sub_commands.insert(0, ("compile_catalog", None)) - cmdclass["compile_catalog"] = babel.compile_catalog cmdclass["extract_messages"] = babel.extract_messages cmdclass["update_catalog"] = babel.update_catalog cmdclass["init_catalog"] = babel.init_catalog if using_sphinx: - build.sub_commands.insert(0, ("build_sphinx_man", None)) cmdclass["build_sphinx_man"] = BuildDoc if platform.system() in ("FreeBSD", "OpenBSD"): @@ -378,6 +379,7 @@ class build(_orig_build): if os.path.exists(os.path.join(translations_dir, mo_subpath)): mo_files.append(mo_subpath) + setup( version=version, data_files=data_files, @@ -388,6 +390,7 @@ class build(_orig_build): }, cmdclass=cmdclass, test_suite="bpython.test", + zip_safe=False, ) # vim: fileencoding=utf-8 sw=4 ts=4 sts=4 ai et sta From c085f9cc519494970e09f4a77aaeadf0bff77f87 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 1 Jun 2024 23:20:39 +0200 Subject: [PATCH 1590/1650] CI: allow sphinx >= 7 --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 37128f249..de2f98ccc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -33,7 +33,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5,<7" + pip install urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" pip install pytest pytest-cov numpy - name: Build with Python ${{ matrix.python-version }} run: | From 7238851ca65ec0381928c1ef8ef0bde475bf6a50 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 1 Jun 2024 23:24:07 +0200 Subject: [PATCH 1591/1650] Also register BuildDoc for build_sphinx --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 9e24203fd..de10eaf44 100755 --- a/setup.py +++ b/setup.py @@ -341,6 +341,7 @@ def run(self): cmdclass["init_catalog"] = babel.init_catalog if using_sphinx: + cmdclass["build_sphinx"] = BuildDoc cmdclass["build_sphinx_man"] = BuildDoc if platform.system() in ("FreeBSD", "OpenBSD"): From 23294503c59088c5ea9c3d811d073d14272f9ff0 Mon Sep 17 00:00:00 2001 From: suman Date: Sat, 1 Jun 2024 23:43:24 +0545 Subject: [PATCH 1592/1650] Replace NoReturn with Never --- bpython/args.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index ed0b0055b..e8e882a22 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -36,7 +36,7 @@ import os import sys from pathlib import Path -from typing import Tuple, List, Optional, NoReturn, Callable +from typing import Tuple, List, Optional, Never, Callable from types import ModuleType from . import __version__, __copyright__ @@ -51,7 +51,7 @@ class ArgumentParserFailed(ValueError): class RaisingArgumentParser(argparse.ArgumentParser): - def error(self, msg: str) -> NoReturn: + def error(self, msg: str) -> Never: raise ArgumentParserFailed() From f8aeaaf8ef8b513473267db99dfe845d0e4b1a41 Mon Sep 17 00:00:00 2001 From: suman Date: Sat, 1 Jun 2024 23:43:46 +0545 Subject: [PATCH 1593/1650] Add type annotations --- bpdb/debugger.py | 8 ++++---- bpython/args.py | 4 ++-- bpython/urwid.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bpdb/debugger.py b/bpdb/debugger.py index 3e5bbc91b..38469541a 100644 --- a/bpdb/debugger.py +++ b/bpdb/debugger.py @@ -27,24 +27,24 @@ class BPdb(pdb.Pdb): """PDB with BPython support.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.prompt = "(BPdb) " self.intro = 'Use "B" to enter bpython, Ctrl-d to exit it.' - def postloop(self): + def postloop(self) -> None: # We only want to show the intro message once. self.intro = None super().postloop() # cmd.Cmd commands - def do_Bpython(self, arg): + def do_Bpython(self, arg: str) -> None: locals_ = self.curframe.f_globals.copy() locals_.update(self.curframe.f_locals) bpython.embed(locals_, ["-i"]) - def help_Bpython(self): + def help_Bpython(self) -> None: print("B(python)") print("") print( diff --git a/bpython/args.py b/bpython/args.py index e8e882a22..ed0b0055b 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -36,7 +36,7 @@ import os import sys from pathlib import Path -from typing import Tuple, List, Optional, Never, Callable +from typing import Tuple, List, Optional, NoReturn, Callable from types import ModuleType from . import __version__, __copyright__ @@ -51,7 +51,7 @@ class ArgumentParserFailed(ValueError): class RaisingArgumentParser(argparse.ArgumentParser): - def error(self, msg: str) -> Never: + def error(self, msg: str) -> NoReturn: raise ArgumentParserFailed() diff --git a/bpython/urwid.py b/bpython/urwid.py index 9b061340f..3c075d937 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -76,10 +76,10 @@ class EvalProtocol(basic.LineOnlyReceiver): delimiter = "\n" - def __init__(self, myrepl): + def __init__(self, myrepl) -> None: self.repl = myrepl - def lineReceived(self, line): + def lineReceived(self, line) -> None: # HACK! # TODO: deal with encoding issues here... self.repl.main_loop.process_input(line) From a12d339e1a0bdca726d439ed1231f3f2ca993eac Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sun, 2 Jun 2024 13:04:12 +0200 Subject: [PATCH 1594/1650] Use Never for Python 3.11 and newer --- bpython/_typing_compat.py | 28 ++++++++++++++++++++++++++++ bpython/args.py | 5 +++-- 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 bpython/_typing_compat.py diff --git a/bpython/_typing_compat.py b/bpython/_typing_compat.py new file mode 100644 index 000000000..83567b4f7 --- /dev/null +++ b/bpython/_typing_compat.py @@ -0,0 +1,28 @@ +# The MIT License +# +# Copyright (c) 2024 Sebastian Ramacher +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +try: + # introduced in Python 3.11 + from typing import Never +except ImportError: + from typing import NoReturn as Never # type: ignore + diff --git a/bpython/args.py b/bpython/args.py index ed0b0055b..55691a2a0 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -36,12 +36,13 @@ import os import sys from pathlib import Path -from typing import Tuple, List, Optional, NoReturn, Callable +from typing import Tuple, List, Optional, Callable from types import ModuleType from . import __version__, __copyright__ from .config import default_config_path, Config from .translations import _ +from ._typing_compat import Never logger = logging.getLogger(__name__) @@ -51,7 +52,7 @@ class ArgumentParserFailed(ValueError): class RaisingArgumentParser(argparse.ArgumentParser): - def error(self, msg: str) -> NoReturn: + def error(self, msg: str) -> Never: raise ArgumentParserFailed() From c152cbf8485695bb1835aef5b58f7fc275f03307 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 13 Jul 2024 17:31:27 +0200 Subject: [PATCH 1595/1650] Update changelog for 0.25 --- CHANGELOG.rst | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d7ecb3ab7..67d56f883 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,18 +6,29 @@ Changelog General information: -* The bpython-cli rendering backend has been removed following deprecation in +* The `bpython-cli` rendering backend has been removed following deprecation in version 0.19. - +* This release is focused on Python 3.12 support. New features: Fixes: +* Fix __signature__ support + Thanks to gpotter2 +* #995: Fix handling of `SystemExit` +* #996: Improve order of completion results + Thanks to gpotter2 +* Fix build of documentation and manpages with Sphinx >= 7 +* #1001: Do not fail if modules don't have __version__ Changes to dependencies: +* Remove use of distutils + Thanks to Anderson Bravalheri + +Support for Python 3.12 has been added. Support for Python 3.7 has been dropped. 0.24 ---- @@ -37,7 +48,7 @@ Fixes: Changes to dependencies: -* wheel is no required as part of pyproject.toml's build dependencies +* wheel is not required as part of pyproject.toml's build dependencies Support for Python 3.11 has been added. From ce710bbdb48be7ec8325d01ee156ba666e258c63 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 13 Jul 2024 17:57:18 +0200 Subject: [PATCH 1596/1650] Format with black --- bpython/_typing_compat.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bpython/_typing_compat.py b/bpython/_typing_compat.py index 83567b4f7..486aacafc 100644 --- a/bpython/_typing_compat.py +++ b/bpython/_typing_compat.py @@ -25,4 +25,3 @@ from typing import Never except ImportError: from typing import NoReturn as Never # type: ignore - From b6318376b255be645b16aaf27c6475359e384ab9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Sat, 13 Jul 2024 21:27:49 +0200 Subject: [PATCH 1597/1650] Update copyright year --- bpython/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/__init__.py b/bpython/__init__.py index 8c7bfb37a..26fa3e63d 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -31,7 +31,7 @@ __author__ = ( "Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al." ) -__copyright__ = f"(C) 2008-2023 {__author__}" +__copyright__ = f"(C) 2008-2024 {__author__}" __license__ = "MIT" __version__ = version package_dir = os.path.abspath(os.path.dirname(__file__)) From f0c023071a8f21b2007389ffcbe57399f35f0cac Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 25 Oct 2024 11:28:44 +0200 Subject: [PATCH 1598/1650] CI: test with Python 3.13 --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index de2f98ccc..747d55a44 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -20,6 +20,7 @@ jobs: - "3.10" - "3.11" - "3.12" + - "3.13" - "pypy-3.8" steps: - uses: actions/checkout@v4 From bbdff64fe37b851b6a33183a6a35739bbb4687a0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 25 Oct 2024 11:45:34 +0200 Subject: [PATCH 1599/1650] Accept source in showsyntaxerror --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index f30cfa31d..b048314d6 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -152,7 +152,7 @@ def runsource( with self.timer: return super().runsource(source, filename, symbol) - def showsyntaxerror(self, filename: Optional[str] = None) -> None: + def showsyntaxerror(self, filename: Optional[str] = None, source: Optional[str] = None) -> None: """Override the regular handler, the code's copied and pasted from code.py, as per showtraceback, but with the syntaxerror callback called and the text in a pretty colour.""" From 52a7a157037f1d8ef81bd0672b636b837d90bed6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 25 Oct 2024 17:42:06 +0200 Subject: [PATCH 1600/1650] Switch assert arguments to display correct value as expected --- bpython/test/test_interpreter.py | 4 ++-- bpython/test/test_repl.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index a4a32dd09..acad12c1d 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -130,8 +130,8 @@ def gfunc(): ) a = i.a - self.assertMultiLineEqual(str(plain("").join(a)), str(expected)) - self.assertEqual(plain("").join(a), expected) + self.assertMultiLineEqual(str(expected), str(plain("").join(a))) + self.assertEqual(expected, plain("").join(a)) def test_getsource_works_on_interactively_defined_functions(self): source = "def foo(x):\n return x + 1\n" diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 8c3b85cc8..3f6b7c124 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -307,7 +307,7 @@ def assert_get_source_error_for_current_function(self, func, msg): try: self.repl.get_source_of_current_name() except repl.SourceNotFound as e: - self.assertEqual(e.args[0], msg) + self.assertEqual(msg, e.args[0]) else: self.fail("Should have raised SourceNotFound") From 45f4117b534d6827279f7b9e633f3cabe0fb37e6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 25 Oct 2024 17:42:20 +0200 Subject: [PATCH 1601/1650] Fix test errors with Python 3.13 --- bpython/test/test_interpreter.py | 17 ++++++++++++++++- bpython/test/test_repl.py | 11 ++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index acad12c1d..b9f0a31e2 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -99,7 +99,22 @@ def gfunc(): global_not_found = "name 'gfunc' is not defined" - if (3, 11) <= sys.version_info[:2]: + if (3, 13) <= sys.version_info[:2]: + expected = ( + "Traceback (most recent call last):\n File " + + green('""') + + ", line " + + bold(magenta("1")) + + ", in " + + cyan("") + + "\n gfunc()" + + "\n ^^^^^\n" + + bold(red("NameError")) + + ": " + + cyan(global_not_found) + + "\n" + ) + elif (3, 11) <= sys.version_info[:2]: expected = ( "Traceback (most recent call last):\n File " + green('""') diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 3f6b7c124..5cafec946 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -332,9 +332,14 @@ def test_current_function_cpython(self): self.assert_get_source_error_for_current_function( collections.defaultdict.copy, "No source code found for INPUTLINE" ) - self.assert_get_source_error_for_current_function( - collections.defaultdict, "could not find class definition" - ) + if sys.version_info[:2] >= (3, 13): + self.assert_get_source_error_for_current_function( + collections.defaultdict, "source code not available" + ) + else: + self.assert_get_source_error_for_current_function( + collections.defaultdict, "could not find class definition" + ) def test_current_line(self): self.repl.interp.locals["a"] = socket.socket From 4605fabe156a9bf1abfee5945514cf81c1828df2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 04:28:01 +0000 Subject: [PATCH 1602/1650] Bump codecov/codecov-action from 4 to 5 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 747d55a44..7ccef9bce 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -47,7 +47,7 @@ jobs: run: | pytest --cov=bpython --cov-report=xml -v - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 env: PYTHON_VERSION: ${{ matrix.python-version }} with: From c332f0d684160a612950bcf88d1a1cfec50b4ab2 Mon Sep 17 00:00:00 2001 From: Max R Date: Mon, 16 Dec 2024 10:00:04 -0500 Subject: [PATCH 1603/1650] black --- bpython/repl.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index b048314d6..c87d1965b 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -152,7 +152,9 @@ def runsource( with self.timer: return super().runsource(source, filename, symbol) - def showsyntaxerror(self, filename: Optional[str] = None, source: Optional[str] = None) -> None: + def showsyntaxerror( + self, filename: Optional[str] = None, source: Optional[str] = None + ) -> None: """Override the regular handler, the code's copied and pasted from code.py, as per showtraceback, but with the syntaxerror callback called and the text in a pretty colour.""" From d3e7a174831c08c91d0574ae05eebe0eb9bf4cb9 Mon Sep 17 00:00:00 2001 From: Max R Date: Mon, 16 Dec 2024 10:05:38 -0500 Subject: [PATCH 1604/1650] codespell --- .github/workflows/lint.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d960c6d89..fbb5d996b 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -24,8 +24,8 @@ jobs: - uses: actions/checkout@v4 - uses: codespell-project/actions-codespell@master with: - skip: '*.po' - ignore_words_list: ba,te,deltion,dedent,dedented + skip: '*.po',encoding_latin1.py + ignore_words_list: ba,te,deltion,dedent,dedented,assertIn mypy: runs-on: ubuntu-latest From c70fa70b8fdd57fa25d7c8890727d17e78397e27 Mon Sep 17 00:00:00 2001 From: Max R Date: Mon, 16 Dec 2024 10:39:23 -0500 Subject: [PATCH 1605/1650] mypy --- bpython/_typing_compat.py | 2 +- bpython/curtsiesfrontend/repl.py | 3 ++- bpython/pager.py | 3 ++- bpython/test/test_preprocess.py | 3 ++- setup.cfg | 1 + 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bpython/_typing_compat.py b/bpython/_typing_compat.py index 486aacafc..5d9a36079 100644 --- a/bpython/_typing_compat.py +++ b/bpython/_typing_compat.py @@ -24,4 +24,4 @@ # introduced in Python 3.11 from typing import Never except ImportError: - from typing import NoReturn as Never # type: ignore + from typing_extensions import Never # type: ignore diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 302e67d4c..69eb18c9b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -236,7 +236,8 @@ def close(self) -> None: @property def encoding(self) -> str: - return sys.__stdin__.encoding + # `encoding` is new in py39 + return sys.__stdin__.encoding # type: ignore # TODO write a read() method? diff --git a/bpython/pager.py b/bpython/pager.py index e145e0ed8..65a3b223c 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -55,7 +55,8 @@ def page(data: str, use_internal: bool = False) -> None: try: popen = subprocess.Popen(command, stdin=subprocess.PIPE) assert popen.stdin is not None - data_bytes = data.encode(sys.__stdout__.encoding, "replace") + # `encoding` is new in py39 + data_bytes = data.encode(sys.__stdout__.encoding, "replace") # type: ignore popen.stdin.write(data_bytes) popen.stdin.close() except OSError as e: diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index e9309f1e1..a72a64b63 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -4,6 +4,7 @@ import unittest from code import compile_command as compiler +from codeop import CommandCompiler from functools import partial from bpython.curtsiesfrontend.interpreter import code_finished_will_parse @@ -11,7 +12,7 @@ from bpython.test.fodder import original, processed -preproc = partial(preprocess, compiler=compiler) +preproc = partial(preprocess, compiler=CommandCompiler) def get_fodder_source(test_name): diff --git a/setup.cfg b/setup.cfg index 1fe4a6f99..9a4f0bcbe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,6 +29,7 @@ install_requires = pygments pyxdg requests + typing_extensions ; python_version < "3.11" [options.extras_require] clipboard = pyperclip From 839145e913d72eb025f61086a09f133a0230350f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 15 Jan 2025 23:53:17 +0100 Subject: [PATCH 1606/1650] Handle title argument of pydoc.pager (fixes #1029) --- bpython/curtsiesfrontend/_internal.py | 4 ++-- bpython/curtsiesfrontend/repl.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 0480c1b06..16a598fa3 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -52,8 +52,8 @@ def __init__(self, repl=None): super().__init__() - def pager(self, output): - self._repl.pager(output) + def pager(self, output, title=""): + self._repl.pager(output, title) def __call__(self, *args, **kwargs): if self._repl.reevaluating: diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 69eb18c9b..01b047118 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -2101,10 +2101,10 @@ def focus_on_subprocess(self, args): finally: signal.signal(signal.SIGWINCH, prev_sigwinch_handler) - def pager(self, text: str) -> None: - """Runs an external pager on text + def pager(self, text: str, title: str = "") -> None: + """Runs an external pager on text""" - text must be a str""" + # TODO: make less handle title command = get_pager_command() with tempfile.NamedTemporaryFile() as tmp: tmp.write(text.encode(getpreferredencoding())) From f5d6da985b30091c0d4dda67d35a4877200ea344 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 16 Jan 2025 00:07:33 +0100 Subject: [PATCH 1607/1650] Drop support for Python 3.8 --- .github/workflows/build.yaml | 81 +++++++++++++++--------------- bpython/simpleeval.py | 23 ++------- doc/sphinx/source/contributing.rst | 2 +- doc/sphinx/source/releases.rst | 2 +- pyproject.toml | 6 +-- setup.cfg | 2 +- 6 files changed, 48 insertions(+), 68 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7ccef9bce..a6e9aef0a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,53 +4,52 @@ on: push: pull_request: schedule: - # run at 7:00 on the first of every month - - cron: '0 7 1 * *' + # run at 7:00 on the first of every month + - cron: "0 7 1 * *" jobs: build: runs-on: ubuntu-latest - continue-on-error: ${{ matrix.python-version == 'pypy-3.8' }} + continue-on-error: ${{ matrix.python-version == 'pypy-3.9' }} strategy: fail-fast: false matrix: python-version: - - "3.8" - - "3.9" - - "3.10" - - "3.11" - - "3.12" - - "3.13" - - "pypy-3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "pypy-3.9" steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" - pip install pytest pytest-cov numpy - - name: Build with Python ${{ matrix.python-version }} - run: | - python setup.py build - - name: Build documentation - run: | - python setup.py build_sphinx - python setup.py build_sphinx_man - - name: Test with pytest - run: | - pytest --cov=bpython --cov-report=xml -v - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 - env: - PYTHON_VERSION: ${{ matrix.python-version }} - with: - file: ./coverage.xml - env_vars: PYTHON_VERSION - if: ${{ always() }} + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" + pip install pytest pytest-cov numpy + - name: Build with Python ${{ matrix.python-version }} + run: | + python setup.py build + - name: Build documentation + run: | + python setup.py build_sphinx + python setup.py build_sphinx_man + - name: Test with pytest + run: | + pytest --cov=bpython --cov-report=xml -v + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + env: + PYTHON_VERSION: ${{ matrix.python-version }} + with: + file: ./coverage.xml + env_vars: PYTHON_VERSION + if: ${{ always() }} diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index c5bba43db..3f334af4a 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -33,12 +33,9 @@ from . import line as line_properties from .inspection import getattr_safe -_is_py38 = sys.version_info[:2] >= (3, 8) -_is_py39 = sys.version_info[:2] >= (3, 9) - _string_type_nodes = (ast.Str, ast.Bytes) _numeric_types = (int, float, complex) -_name_type_nodes = (ast.Name,) if _is_py38 else (ast.Name, ast.NameConstant) +_name_type_nodes = (ast.Name,) class EvaluationError(Exception): @@ -91,10 +88,6 @@ def simple_eval(node_or_string, namespace=None): def _convert(node): if isinstance(node, ast.Constant): return node.value - elif not _is_py38 and isinstance(node, _string_type_nodes): - return node.s - elif not _is_py38 and isinstance(node, ast.Num): - return node.n elif isinstance(node, ast.Tuple): return tuple(map(_convert, node.elts)) elif isinstance(node, ast.List): @@ -168,18 +161,8 @@ def _convert(node): return left - right # this is a deviation from literal_eval: we allow indexing - elif ( - not _is_py39 - and isinstance(node, ast.Subscript) - and isinstance(node.slice, ast.Index) - ): - obj = _convert(node.value) - index = _convert(node.slice.value) - return safe_getitem(obj, index) - elif ( - _is_py39 - and isinstance(node, ast.Subscript) - and isinstance(node.slice, (ast.Constant, ast.Name)) + elif isinstance(node, ast.Subscript) and isinstance( + node.slice, (ast.Constant, ast.Name) ): obj = _convert(node.value) index = _convert(node.slice) diff --git a/doc/sphinx/source/contributing.rst b/doc/sphinx/source/contributing.rst index 32b1ea869..3b93089df 100644 --- a/doc/sphinx/source/contributing.rst +++ b/doc/sphinx/source/contributing.rst @@ -17,7 +17,7 @@ the time of day. Getting your development environment set up ------------------------------------------- -bpython supports Python 3.8 and newer. The code is compatible with all +bpython supports Python 3.9 and newer. The code is compatible with all supported versions. Using a virtual environment is probably a good idea. Create a virtual diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst index fcce5c1cf..7d789f166 100644 --- a/doc/sphinx/source/releases.rst +++ b/doc/sphinx/source/releases.rst @@ -45,7 +45,7 @@ A checklist to perform some manual tests before a release: Check that all of the following work before a release: -* Runs under Python 3.8 - 3.11 +* Runs under Python 3.9 - 3.13 * Save * Rewind * Pastebin diff --git a/pyproject.toml b/pyproject.toml index 924722b0b..ca4e04508 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,10 @@ [build-system] -requires = [ - "setuptools >= 62.4.0", -] +requires = ["setuptools >= 62.4.0"] build-backend = "setuptools.build_meta" [tool.black] line-length = 80 -target_version = ["py38"] +target_version = ["py39"] include = '\.pyi?$' exclude = ''' /( diff --git a/setup.cfg b/setup.cfg index 9a4f0bcbe..f8b7c3258 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,7 +14,7 @@ classifiers = Programming Language :: Python :: 3 [options] -python_requires = >=3.8 +python_requires = >=3.9 packages = bpython bpython.curtsiesfrontend From b8923057a2aefe55c97164ff36f7766c82b7ea0b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 16 Jan 2025 00:14:17 +0100 Subject: [PATCH 1608/1650] Update changelog for 0.25 --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 67d56f883..114b94401 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,7 @@ General information: * The `bpython-cli` rendering backend has been removed following deprecation in version 0.19. -* This release is focused on Python 3.12 support. +* This release is focused on Python 3.13 support. New features: @@ -28,7 +28,7 @@ Changes to dependencies: * Remove use of distutils Thanks to Anderson Bravalheri -Support for Python 3.12 has been added. Support for Python 3.7 has been dropped. +Support for Python 3.12 and 3.13 has been added. Support for Python 3.7 and 3.8 has been dropped. 0.24 ---- From 5b31cca96c951ddefba8f2c71a4abc208b2adac0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 16 Jan 2025 00:16:37 +0100 Subject: [PATCH 1609/1650] CI: fix yaml --- .github/workflows/lint.yaml | 54 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index fbb5d996b..b60561592 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -8,38 +8,38 @@ jobs: black: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install black codespell - - name: Check with black - run: black --check . + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install black codespell + - name: Check with black + run: black --check . codespell: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: codespell-project/actions-codespell@master - with: - skip: '*.po',encoding_latin1.py - ignore_words_list: ba,te,deltion,dedent,dedented,assertIn + - uses: actions/checkout@v4 + - uses: codespell-project/actions-codespell@master + with: + skip: "*.po,encoding_latin1.py" + ignore_words_list: ba,te,deltion,dedent,dedented,assertIn mypy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install mypy - pip install -r requirements.txt - pip install urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" numpy - pip install types-backports types-requests types-setuptools types-toml types-pygments - - name: Check with mypy - # for now only run on a few files to avoid slipping backward - run: mypy + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install mypy + pip install -r requirements.txt + pip install urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" numpy + pip install types-backports types-requests types-setuptools types-toml types-pygments + - name: Check with mypy + # for now only run on a few files to avoid slipping backward + run: mypy From 400f5eda1ae7859b88c81d591177b54128d0e835 Mon Sep 17 00:00:00 2001 From: Pushkar Kulkarni Date: Thu, 16 Jan 2025 11:02:07 +0530 Subject: [PATCH 1610/1650] More general adaptation of showsyntaxerror() to Python 3.13 Python 3.13's code.InteractiveInterpreter adds a new **kwargs argument to its showsyntaxerror() method. Currently, the only use of it is to send a named argument of name "source". Whilst the current adapation of repl.Interpreter is specific and should work in the short term, here is a more general solution. --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index c87d1965b..0374bb6bb 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -153,7 +153,7 @@ def runsource( return super().runsource(source, filename, symbol) def showsyntaxerror( - self, filename: Optional[str] = None, source: Optional[str] = None + self, filename: Optional[str] = None, **kwargs ) -> None: """Override the regular handler, the code's copied and pasted from code.py, as per showtraceback, but with the syntaxerror callback called From a4eadd750d2d3b103bb78abaac717358f7efc722 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 17 Jan 2025 21:21:40 +0100 Subject: [PATCH 1611/1650] Start development of 0.26 --- CHANGELOG.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 114b94401..f55fe76fd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,22 @@ Changelog ========= +0.26 +---- + +General information: + + +New features: + + +Fixes: + + +Changes to dependencies: + + + 0.25 ---- From 9b344248ac8a180f4c54dc4b2eb6940596c6067b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 17 Jan 2025 21:44:57 +0100 Subject: [PATCH 1612/1650] Upgrade code to 3.9+ --- bpython/args.py | 8 ++-- bpython/autocomplete.py | 54 ++++++++++++------------- bpython/config.py | 5 ++- bpython/curtsies.py | 13 +++--- bpython/curtsiesfrontend/_internal.py | 2 +- bpython/curtsiesfrontend/events.py | 2 +- bpython/curtsiesfrontend/filewatch.py | 7 ++-- bpython/curtsiesfrontend/interpreter.py | 9 +++-- bpython/curtsiesfrontend/parse.py | 4 +- bpython/curtsiesfrontend/preprocess.py | 2 +- bpython/curtsiesfrontend/repl.py | 27 ++++++------- bpython/filelock.py | 2 +- bpython/formatter.py | 3 +- bpython/history.py | 13 +++--- bpython/importcompletion.py | 15 +++---- bpython/inspection.py | 16 ++++---- bpython/keys.py | 4 +- bpython/lazyre.py | 4 +- bpython/line.py | 2 +- bpython/pager.py | 2 +- bpython/paste.py | 6 +-- bpython/patch_linecache.py | 4 +- bpython/repl.py | 52 ++++++++++++------------ bpython/simpleeval.py | 4 +- bpython/test/test_curtsies_repl.py | 2 +- bpython/test/test_inspection.py | 6 +-- bpython/test/test_line_properties.py | 2 +- bpython/test/test_repl.py | 2 +- bpython/translations/__init__.py | 2 +- 29 files changed, 139 insertions(+), 135 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 55691a2a0..1eb59a691 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -73,14 +73,14 @@ def log_version(module: ModuleType, name: str) -> None: logger.info("%s: %s", name, module.__version__ if hasattr(module, "__version__") else "unknown version") # type: ignore -Options = Tuple[str, str, Callable[[argparse._ArgumentGroup], None]] +Options = tuple[str, str, Callable[[argparse._ArgumentGroup], None]] def parse( - args: Optional[List[str]], + args: Optional[list[str]], extras: Optional[Options] = None, ignore_stdin: bool = False, -) -> Tuple[Config, argparse.Namespace, List[str]]: +) -> tuple[Config, argparse.Namespace, list[str]]: """Receive an argument list - if None, use sys.argv - parse all args and take appropriate action. Also receive optional extra argument: this should be a tuple of (title, description, callback) @@ -256,7 +256,7 @@ def callback(group): def exec_code( - interpreter: code.InteractiveInterpreter, args: List[str] + interpreter: code.InteractiveInterpreter, args: list[str] ) -> None: """ Helper to execute code in a given interpreter, e.g. to implement the behavior of python3 [-i] file.py diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 000fbde9f..88afbe54f 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -40,13 +40,13 @@ from typing import ( Any, Dict, - Iterator, List, Optional, - Sequence, Set, Tuple, ) +from collections.abc import Iterator, Sequence + from . import inspection from . import line as lineparts from .line import LinePart @@ -236,7 +236,7 @@ def __init__( @abc.abstractmethod def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: """Returns a list of possible matches given a line and cursor, or None if this completion type isn't applicable. @@ -268,7 +268,7 @@ def format(self, word: str) -> str: def substitute( self, cursor_offset: int, line: str, match: str - ) -> Tuple[int, str]: + ) -> tuple[int, str]: """Returns a cursor offset and line with match swapped in""" lpart = self.locate(cursor_offset, line) assert lpart @@ -311,7 +311,7 @@ def format(self, word: str) -> str: def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: return_value = None all_matches = set() for completer in self._completers: @@ -336,7 +336,7 @@ def __init__( def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: return self.module_gatherer.complete(cursor_offset, line) def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: @@ -356,7 +356,7 @@ def __init__(self, mode: AutocompleteModes = AutocompleteModes.SIMPLE): def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: cs = lineparts.current_string(cursor_offset, line) if cs is None: return None @@ -389,9 +389,9 @@ def matches( cursor_offset: int, line: str, *, - locals_: Optional[Dict[str, Any]] = None, + locals_: Optional[dict[str, Any]] = None, **kwargs: Any, - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: r = self.locate(cursor_offset, line) if r is None: return None @@ -421,7 +421,7 @@ def format(self, word: str) -> str: return _after_last_dot(word) def attr_matches( - self, text: str, namespace: Dict[str, Any] + self, text: str, namespace: dict[str, Any] ) -> Iterator[str]: """Taken from rlcompleter.py and bent to my will.""" @@ -460,7 +460,7 @@ def attr_lookup(self, obj: Any, expr: str, attr: str) -> Iterator[str]: if self.method_match(word, n, attr) and word != "__builtins__" ) - def list_attributes(self, obj: Any) -> List[str]: + def list_attributes(self, obj: Any) -> list[str]: # TODO: re-implement dir without AttrCleaner here # # Note: accessing `obj.__dir__` via `getattr_static` is not side-effect free. @@ -474,9 +474,9 @@ def matches( cursor_offset: int, line: str, *, - locals_: Optional[Dict[str, Any]] = None, + locals_: Optional[dict[str, Any]] = None, **kwargs: Any, - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: if locals_ is None: return None @@ -516,7 +516,7 @@ def matches( current_block: Optional[str] = None, complete_magic_methods: Optional[bool] = None, **kwargs: Any, - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: if ( current_block is None or complete_magic_methods is None @@ -541,9 +541,9 @@ def matches( cursor_offset: int, line: str, *, - locals_: Optional[Dict[str, Any]] = None, + locals_: Optional[dict[str, Any]] = None, **kwargs: Any, - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace that match. @@ -583,7 +583,7 @@ def matches( *, funcprops: Optional[inspection.FuncProps] = None, **kwargs: Any, - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: if funcprops is None: return None @@ -622,9 +622,9 @@ def matches( cursor_offset: int, line: str, *, - locals_: Optional[Dict[str, Any]] = None, + locals_: Optional[dict[str, Any]] = None, **kwargs: Any, - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: if locals_ is None: locals_ = __main__.__dict__ @@ -648,7 +648,7 @@ def matches( class MultilineJediCompletion(BaseCompletionType): # type: ignore [no-redef] def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: return None def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: @@ -665,9 +665,9 @@ def matches( line: str, *, current_block: Optional[str] = None, - history: Optional[List[str]] = None, + history: Optional[list[str]] = None, **kwargs: Any, - ) -> Optional[Set[str]]: + ) -> Optional[set[str]]: if ( current_block is None or history is None @@ -725,12 +725,12 @@ def get_completer( cursor_offset: int, line: str, *, - locals_: Optional[Dict[str, Any]] = None, + locals_: Optional[dict[str, Any]] = None, argspec: Optional[inspection.FuncProps] = None, - history: Optional[List[str]] = None, + history: Optional[list[str]] = None, current_block: Optional[str] = None, complete_magic_methods: Optional[bool] = None, -) -> Tuple[List[str], Optional[BaseCompletionType]]: +) -> tuple[list[str], Optional[BaseCompletionType]]: """Returns a list of matches and an applicable completer If no matches available, returns a tuple of an empty list and None @@ -747,7 +747,7 @@ def get_completer( double underscore methods like __len__ in method signatures """ - def _cmpl_sort(x: str) -> Tuple[bool, str]: + def _cmpl_sort(x: str) -> tuple[bool, str]: """ Function used to sort the matches. """ @@ -784,7 +784,7 @@ def _cmpl_sort(x: str) -> Tuple[bool, str]: def get_default_completer( mode: AutocompleteModes, module_gatherer: ModuleGatherer -) -> Tuple[BaseCompletionType, ...]: +) -> tuple[BaseCompletionType, ...]: return ( ( DictKeyCompletion(mode=mode), diff --git a/bpython/config.py b/bpython/config.py index 5123ec226..27af87402 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -31,7 +31,8 @@ from configparser import ConfigParser from itertools import chain from pathlib import Path -from typing import MutableMapping, Mapping, Any, Dict +from typing import Any, Dict +from collections.abc import MutableMapping, Mapping from xdg import BaseDirectory from .autocomplete import AutocompleteModes @@ -115,7 +116,7 @@ class Config: "right_arrow_suggestion": "K", } - defaults: Dict[str, Dict[str, Any]] = { + defaults: dict[str, dict[str, Any]] = { "general": { "arg_spec": True, "auto_display_list": True, diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 11b960503..547a853ee 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -25,14 +25,13 @@ Any, Callable, Dict, - Generator, List, Optional, Protocol, - Sequence, Tuple, Union, ) +from collections.abc import Generator, Sequence logger = logging.getLogger(__name__) @@ -51,7 +50,7 @@ class FullCurtsiesRepl(BaseRepl): def __init__( self, config: Config, - locals_: Optional[Dict[str, Any]] = None, + locals_: Optional[dict[str, Any]] = None, banner: Optional[str] = None, interp: Optional[Interp] = None, ) -> None: @@ -111,7 +110,7 @@ def interrupting_refresh(self) -> None: def request_undo(self, n: int = 1) -> None: return self._request_undo_callback(n=n) - def get_term_hw(self) -> Tuple[int, int]: + def get_term_hw(self) -> tuple[int, int]: return self.window.get_term_hw() def get_cursor_vertical_diff(self) -> int: @@ -179,8 +178,8 @@ def mainloop( def main( - args: Optional[List[str]] = None, - locals_: Optional[Dict[str, Any]] = None, + args: Optional[list[str]] = None, + locals_: Optional[dict[str, Any]] = None, banner: Optional[str] = None, welcome_message: Optional[str] = None, ) -> Any: @@ -209,7 +208,7 @@ def curtsies_arguments(parser: argparse._ArgumentGroup) -> None: interp = None paste = None - exit_value: Tuple[Any, ...] = () + exit_value: tuple[Any, ...] = () if exec_args: if not options: raise ValueError("don't pass in exec_args without options") diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 16a598fa3..cb7b81057 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -34,7 +34,7 @@ def __enter__(self): def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: diff --git a/bpython/curtsiesfrontend/events.py b/bpython/curtsiesfrontend/events.py index 26f105dc9..4f9c13e55 100644 --- a/bpython/curtsiesfrontend/events.py +++ b/bpython/curtsiesfrontend/events.py @@ -1,7 +1,7 @@ """Non-keyboard events used in bpython curtsies REPL""" import time -from typing import Sequence +from collections.abc import Sequence import curtsies.events diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index e70325ab5..53ae47844 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -1,6 +1,7 @@ import os from collections import defaultdict -from typing import Callable, Dict, Iterable, Sequence, Set, List +from typing import Callable, Dict, Set, List +from collections.abc import Iterable, Sequence from .. import importcompletion @@ -20,9 +21,9 @@ def __init__( paths: Iterable[str], on_change: Callable[[Sequence[str]], None], ) -> None: - self.dirs: Dict[str, Set[str]] = defaultdict(set) + self.dirs: dict[str, set[str]] = defaultdict(set) self.on_change = on_change - self.modules_to_add_later: List[str] = [] + self.modules_to_add_later: list[str] = [] self.observer = Observer() self.started = False self.activated = False diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 82e28091c..6532d9688 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,6 +1,7 @@ import sys from codeop import CommandCompiler -from typing import Any, Dict, Iterable, Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union +from collections.abc import Iterable from pygments.token import Generic, Token, Keyword, Name, Comment, String from pygments.token import Error, Literal, Number, Operator, Punctuation @@ -47,7 +48,7 @@ class BPythonFormatter(Formatter): def __init__( self, - color_scheme: Dict[_TokenType, str], + color_scheme: dict[_TokenType, str], **options: Union[str, bool, None], ) -> None: self.f_strings = {k: f"\x01{v}" for k, v in color_scheme.items()} @@ -67,7 +68,7 @@ def format(self, tokensource, outfile): class Interp(ReplInterpreter): def __init__( self, - locals: Optional[Dict[str, Any]] = None, + locals: Optional[dict[str, Any]] = None, ) -> None: """Constructor. @@ -121,7 +122,7 @@ def format(self, tbtext: str, lexer: Any) -> None: def code_finished_will_parse( s: str, compiler: CommandCompiler -) -> Tuple[bool, bool]: +) -> tuple[bool, bool]: """Returns a tuple of whether the buffer could be complete and whether it will parse diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 88a149a65..96e91e55e 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -60,7 +60,7 @@ def parse(s: str) -> FmtStr: ) -def fs_from_match(d: Dict[str, Any]) -> FmtStr: +def fs_from_match(d: dict[str, Any]) -> FmtStr: atts = {} color = "default" if d["fg"]: @@ -99,7 +99,7 @@ def fs_from_match(d: Dict[str, Any]) -> FmtStr: ) -def peel_off_string(s: str) -> Tuple[Dict[str, Any], str]: +def peel_off_string(s: str) -> tuple[dict[str, Any], str]: m = peel_off_string_re.match(s) assert m, repr(s) d = m.groupdict() diff --git a/bpython/curtsiesfrontend/preprocess.py b/bpython/curtsiesfrontend/preprocess.py index 5e59dd499..f48a79bf7 100644 --- a/bpython/curtsiesfrontend/preprocess.py +++ b/bpython/curtsiesfrontend/preprocess.py @@ -2,7 +2,7 @@ etc)""" from codeop import CommandCompiler -from typing import Match +from re import Match from itertools import tee, islice, chain from ..lazyre import LazyReCompile diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 01b047118..09f73a82f 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -14,16 +14,15 @@ from types import FrameType, TracebackType from typing import ( Any, - Iterable, Dict, List, Literal, Optional, - Sequence, Tuple, Type, Union, ) +from collections.abc import Iterable, Sequence import greenlet from curtsies import ( @@ -121,7 +120,7 @@ def __init__( self.current_line = "" self.cursor_offset = 0 self.old_num_lines = 0 - self.readline_results: List[str] = [] + self.readline_results: list[str] = [] if configured_edit_keys is not None: self.rl_char_sequences = configured_edit_keys else: @@ -195,7 +194,7 @@ def readline(self, size: int = -1) -> str: self.readline_results.append(value) return value if size <= -1 else value[:size] - def readlines(self, size: Optional[int] = -1) -> List[str]: + def readlines(self, size: Optional[int] = -1) -> list[str]: if size is None: # the default readlines implementation also accepts None size = -1 @@ -338,10 +337,10 @@ def __init__( self, config: Config, window: CursorAwareWindow, - locals_: Optional[Dict[str, Any]] = None, + locals_: Optional[dict[str, Any]] = None, banner: Optional[str] = None, interp: Optional[Interp] = None, - orig_tcattrs: Optional[List[Any]] = None, + orig_tcattrs: Optional[list[Any]] = None, ): """ locals_ is a mapping of locals to pass into the interpreter @@ -404,7 +403,7 @@ def __init__( # this is every line that's been displayed (input and output) # as with formatting applied. Logical lines that exceeded the terminal width # at the time of output are split across multiple entries in this list. - self.display_lines: List[FmtStr] = [] + self.display_lines: list[FmtStr] = [] # this is every line that's been executed; it gets smaller on rewind self.history = [] @@ -415,11 +414,11 @@ def __init__( # - the first element the line (string, not fmtsr) # - the second element is one of 2 global constants: "input" or "output" # (use LineType.INPUT or LineType.OUTPUT to avoid typing these strings) - self.all_logical_lines: List[Tuple[str, LineType]] = [] + self.all_logical_lines: list[tuple[str, LineType]] = [] # formatted version of lines in the buffer kept around so we can # unhighlight parens using self.reprint_line as called by bpython.Repl - self.display_buffer: List[FmtStr] = [] + self.display_buffer: list[FmtStr] = [] # how many times display has been scrolled down # because there wasn't room to display everything @@ -428,7 +427,7 @@ def __init__( # cursor position relative to start of current_line, 0 is first char self._cursor_offset = 0 - self.orig_tcattrs: Optional[List[Any]] = orig_tcattrs + self.orig_tcattrs: Optional[list[Any]] = orig_tcattrs self.coderunner = CodeRunner(self.interp, self.request_refresh) @@ -460,7 +459,7 @@ def __init__( # some commands act differently based on the prev event # this list doesn't include instances of event.Event, # only keypress-type events (no refresh screen events etc.) - self.last_events: List[Optional[str]] = [None] * 50 + self.last_events: list[Optional[str]] = [None] * 50 # displays prev events in a column on the right hand side self.presentation_mode = False @@ -601,7 +600,7 @@ def __enter__(self): def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: @@ -1561,7 +1560,7 @@ def paint( user_quit=False, try_preserve_history_height=30, min_infobox_height=5, - ) -> Tuple[FSArray, Tuple[int, int]]: + ) -> tuple[FSArray, tuple[int, int]]: """Returns an array of min_height or more rows and width columns, plus cursor position @@ -2237,7 +2236,7 @@ def compress_paste_event(paste_event): def just_simple_events( event_list: Iterable[Union[str, events.Event]] -) -> List[str]: +) -> list[str]: simple_events = [] for e in event_list: if isinstance(e, events.Event): diff --git a/bpython/filelock.py b/bpython/filelock.py index 11f575b6e..5ed8769fd 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -56,7 +56,7 @@ def __enter__(self) -> "BaseLock": def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: diff --git a/bpython/formatter.py b/bpython/formatter.py index f216f213f..8e74ac2c2 100644 --- a/bpython/formatter.py +++ b/bpython/formatter.py @@ -28,7 +28,8 @@ # mypy: disallow_untyped_calls=True -from typing import Any, MutableMapping, Iterable, TextIO +from typing import Any, TextIO +from collections.abc import MutableMapping, Iterable from pygments.formatter import Formatter from pygments.token import ( _TokenType, diff --git a/bpython/history.py b/bpython/history.py index 13dbb5b7f..386214b44 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -25,7 +25,8 @@ from pathlib import Path import stat from itertools import islice, chain -from typing import Iterable, Optional, List, TextIO +from typing import Optional, List, TextIO +from collections.abc import Iterable from .translations import _ from .filelock import FileLock @@ -55,7 +56,7 @@ def __init__( def append(self, line: str) -> None: self.append_to(self.entries, line) - def append_to(self, entries: List[str], line: str) -> None: + def append_to(self, entries: list[str], line: str) -> None: line = line.rstrip("\n") if line: if not self.duplicates: @@ -100,7 +101,7 @@ def entry(self) -> str: return self.entries[-self.index] if self.index else self.saved_line @property - def entries_by_index(self) -> List[str]: + def entries_by_index(self) -> list[str]: return list(chain((self.saved_line,), reversed(self.entries))) def find_match_backward( @@ -196,8 +197,8 @@ def load(self, filename: Path, encoding: str) -> None: with FileLock(hfile, filename=str(filename)): self.entries = self.load_from(hfile) - def load_from(self, fd: TextIO) -> List[str]: - entries: List[str] = [] + def load_from(self, fd: TextIO) -> list[str]: + entries: list[str] = [] for line in fd: self.append_to(entries, line) return entries if len(entries) else [""] @@ -213,7 +214,7 @@ def save(self, filename: Path, encoding: str, lines: int = 0) -> None: self.save_to(hfile, self.entries, lines) def save_to( - self, fd: TextIO, entries: Optional[List[str]] = None, lines: int = 0 + self, fd: TextIO, entries: Optional[list[str]] = None, lines: int = 0 ) -> None: if entries is None: entries = self.entries diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 9df140c64..da1b91405 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -27,7 +27,8 @@ import warnings from dataclasses import dataclass from pathlib import Path -from typing import Optional, Set, Generator, Sequence, Iterable, Union +from typing import Optional, Set, Union +from collections.abc import Generator, Sequence, Iterable from .line import ( current_word, @@ -69,9 +70,9 @@ def __init__( directory names. If `paths` is not given, `sys.path` will be used.""" # Cached list of all known modules - self.modules: Set[str] = set() + self.modules: set[str] = set() # Set of (st_dev, st_ino) to compare against so that paths are not repeated - self.paths: Set[_LoadedInode] = set() + self.paths: set[_LoadedInode] = set() # Patterns to skip self.skiplist: Sequence[str] = ( skiplist if skiplist is not None else tuple() @@ -86,7 +87,7 @@ def __init__( Path(p).resolve() if p else Path.cwd() for p in paths ) - def module_matches(self, cw: str, prefix: str = "") -> Set[str]: + def module_matches(self, cw: str, prefix: str = "") -> set[str]: """Modules names to replace cw with""" full = f"{prefix}.{cw}" if prefix else cw @@ -102,7 +103,7 @@ def module_matches(self, cw: str, prefix: str = "") -> Set[str]: def attr_matches( self, cw: str, prefix: str = "", only_modules: bool = False - ) -> Set[str]: + ) -> set[str]: """Attributes to replace name with""" full = f"{prefix}.{cw}" if prefix else cw module_name, _, name_after_dot = full.rpartition(".") @@ -126,11 +127,11 @@ def attr_matches( return matches - def module_attr_matches(self, name: str) -> Set[str]: + def module_attr_matches(self, name: str) -> set[str]: """Only attributes which are modules to replace name with""" return self.attr_matches(name, only_modules=True) - def complete(self, cursor_offset: int, line: str) -> Optional[Set[str]]: + def complete(self, cursor_offset: int, line: str) -> Optional[set[str]]: """Construct a full list of possibly completions for imports.""" tokens = line.split() if "from" not in tokens and "import" not in tokens: diff --git a/bpython/inspection.py b/bpython/inspection.py index e97a272bc..fb1124ebf 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -62,13 +62,13 @@ def __repr__(self) -> str: @dataclass class ArgSpec: - args: List[str] + args: list[str] varargs: Optional[str] varkwargs: Optional[str] - defaults: Optional[List[_Repr]] - kwonly: List[str] - kwonly_defaults: Optional[Dict[str, _Repr]] - annotations: Optional[Dict[str, Any]] + defaults: Optional[list[_Repr]] + kwonly: list[str] + kwonly_defaults: Optional[dict[str, _Repr]] + annotations: Optional[dict[str, Any]] @dataclass @@ -118,7 +118,7 @@ def __enter__(self) -> None: def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: @@ -134,10 +134,10 @@ def __exit__( return False -def parsekeywordpairs(signature: str) -> Dict[str, str]: +def parsekeywordpairs(signature: str) -> dict[str, str]: preamble = True stack = [] - substack: List[str] = [] + substack: list[str] = [] parendepth = 0 annotation = False for token, value in Python3Lexer().get_tokens(signature): diff --git a/bpython/keys.py b/bpython/keys.py index fe27dbcc4..1068a4f26 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -28,7 +28,7 @@ class KeyMap(Generic[T]): def __init__(self, default: T) -> None: - self.map: Dict[str, T] = {} + self.map: dict[str, T] = {} self.default = default def __getitem__(self, key: str) -> T: @@ -49,7 +49,7 @@ def __setitem__(self, key: str, value: T) -> None: self.map[key] = value -cli_key_dispatch: KeyMap[Tuple[str, ...]] = KeyMap(tuple()) +cli_key_dispatch: KeyMap[tuple[str, ...]] = KeyMap(tuple()) urwid_key_dispatch = KeyMap("") # fill dispatch with letters diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 8d166b74f..d397f05cf 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -21,7 +21,9 @@ # THE SOFTWARE. import re -from typing import Optional, Pattern, Match, Optional, Iterator +from typing import Optional, Optional +from collections.abc import Iterator +from re import Pattern, Match try: from functools import cached_property diff --git a/bpython/line.py b/bpython/line.py index cbc3bf37e..363419fe0 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -291,7 +291,7 @@ def current_expression_attribute( def cursor_on_closing_char_pair( cursor_offset: int, line: str, ch: Optional[str] = None -) -> Tuple[bool, bool]: +) -> tuple[bool, bool]: """Checks if cursor sits on closing character of a pair and whether its pair character is directly behind it """ diff --git a/bpython/pager.py b/bpython/pager.py index 65a3b223c..2fa4846e0 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -33,7 +33,7 @@ from typing import List -def get_pager_command(default: str = "less -rf") -> List[str]: +def get_pager_command(default: str = "less -rf") -> list[str]: command = shlex.split(os.environ.get("PAGER", default)) return command diff --git a/bpython/paste.py b/bpython/paste.py index a81c0c6c9..e846aba3f 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -37,7 +37,7 @@ class PasteFailed(Exception): class Paster(Protocol): - def paste(self, s: str) -> Tuple[str, Optional[str]]: ... + def paste(self, s: str) -> tuple[str, Optional[str]]: ... class PastePinnwand: @@ -45,7 +45,7 @@ def __init__(self, url: str, expiry: str) -> None: self.url = url self.expiry = expiry - def paste(self, s: str) -> Tuple[str, str]: + def paste(self, s: str) -> tuple[str, str]: """Upload to pastebin via json interface.""" url = urljoin(self.url, "/api/v1/paste") @@ -72,7 +72,7 @@ class PasteHelper: def __init__(self, executable: str) -> None: self.executable = executable - def paste(self, s: str) -> Tuple[str, None]: + def paste(self, s: str) -> tuple[str, None]: """Call out to helper program for pastebin upload.""" try: diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index d91392d24..5bf4a45b8 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -9,7 +9,7 @@ class BPythonLinecache(dict): def __init__( self, bpython_history: Optional[ - List[Tuple[int, None, List[str], str]] + list[tuple[int, None, list[str], str]] ] = None, *args, **kwargs, @@ -20,7 +20,7 @@ def __init__( def is_bpython_filename(self, fname: Any) -> bool: return isinstance(fname, str) and fname.startswith(" Tuple[int, None, List[str], str]: + def get_bpython_history(self, key: str) -> tuple[int, None, list[str], str]: """Given a filename provided by remember_bpython_input, returns the associated source string.""" try: diff --git a/bpython/repl.py b/bpython/repl.py index 0374bb6bb..de8890310 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -43,7 +43,6 @@ Any, Callable, Dict, - Iterable, List, Literal, Optional, @@ -53,6 +52,7 @@ Union, cast, ) +from collections.abc import Iterable from pygments.lexers import Python3Lexer from pygments.token import Token, _TokenType @@ -85,7 +85,7 @@ def __enter__(self) -> None: def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Literal[False]: @@ -108,7 +108,7 @@ class Interpreter(code.InteractiveInterpreter): def __init__( self, - locals: Optional[Dict[str, Any]] = None, + locals: Optional[dict[str, Any]] = None, ) -> None: """Constructor. @@ -152,9 +152,7 @@ def runsource( with self.timer: return super().runsource(source, filename, symbol) - def showsyntaxerror( - self, filename: Optional[str] = None, **kwargs - ) -> None: + def showsyntaxerror(self, filename: Optional[str] = None, **kwargs) -> None: """Override the regular handler, the code's copied and pasted from code.py, as per showtraceback, but with the syntaxerror callback called and the text in a pretty colour.""" @@ -221,7 +219,7 @@ def __init__(self) -> None: # word being replaced in the original line of text self.current_word = "" # possible replacements for current_word - self.matches: List[str] = [] + self.matches: list[str] = [] # which word is currently replacing the current word self.index = -1 # cursor position in the original line @@ -265,12 +263,12 @@ def previous(self) -> str: return self.matches[self.index] - def cur_line(self) -> Tuple[int, str]: + def cur_line(self) -> tuple[int, str]: """Returns a cursor offset and line with the current substitution made""" return self.substitute(self.current()) - def substitute(self, match: str) -> Tuple[int, str]: + def substitute(self, match: str) -> tuple[int, str]: """Returns a cursor offset and line with match substituted in""" assert self.completer is not None @@ -286,7 +284,7 @@ def is_cseq(self) -> bool: os.path.commonprefix(self.matches)[len(self.current_word) :] ) - def substitute_cseq(self) -> Tuple[int, str]: + def substitute_cseq(self) -> tuple[int, str]: """Returns a new line by substituting a common sequence in, and update matches""" assert self.completer is not None @@ -307,7 +305,7 @@ def update( self, cursor_offset: int, current_line: str, - matches: List[str], + matches: list[str], completer: autocomplete.BaseCompletionType, ) -> None: """Called to reset the match index and update the word being replaced @@ -428,7 +426,7 @@ def reevaluate(self): @abc.abstractmethod def reprint_line( - self, lineno: int, tokens: List[Tuple[_TokenType, str]] + self, lineno: int, tokens: list[tuple[_TokenType, str]] ) -> None: pass @@ -479,7 +477,7 @@ def __init__(self, interp: Interpreter, config: Config): """ self.config = config self.cut_buffer = "" - self.buffer: List[str] = [] + self.buffer: list[str] = [] self.interp = interp self.interp.syntaxerror_callback = self.clear_current_line self.match = False @@ -488,19 +486,19 @@ def __init__(self, interp: Interpreter, config: Config): ) # all input and output, stored as old style format strings # (\x01, \x02, ...) for cli.py - self.screen_hist: List[str] = [] + self.screen_hist: list[str] = [] # commands executed since beginning of session - self.history: List[str] = [] - self.redo_stack: List[str] = [] + self.history: list[str] = [] + self.redo_stack: list[str] = [] self.evaluating = False self.matches_iter = MatchesIterator() self.funcprops = None self.arg_pos: Union[str, int, None] = None self.current_func = None self.highlighted_paren: Optional[ - Tuple[Any, List[Tuple[_TokenType, str]]] + tuple[Any, list[tuple[_TokenType, str]]] ] = None - self._C: Dict[str, int] = {} + self._C: dict[str, int] = {} self.prev_block_finished: int = 0 self.interact: Interaction = NoInteraction(self.config) # previous pastebin content to prevent duplicate pastes, filled on call @@ -589,7 +587,7 @@ def current_string(self, concatenate=False): def get_object(self, name: str) -> Any: attributes = name.split(".") - obj = eval(attributes.pop(0), cast(Dict[str, Any], self.interp.locals)) + obj = eval(attributes.pop(0), cast(dict[str, Any], self.interp.locals)) while attributes: obj = inspection.getattr_safe(obj, attributes.pop(0)) return obj @@ -597,7 +595,7 @@ def get_object(self, name: str) -> Any: @classmethod def _funcname_and_argnum( cls, line: str - ) -> Tuple[Optional[str], Optional[Union[str, int]]]: + ) -> tuple[Optional[str], Optional[Union[str, int]]]: """Parse out the current function name and arg from a line of code.""" # each element in stack is a _FuncExpr instance # if keyword is not None, we've encountered a keyword and so we're done counting @@ -782,7 +780,7 @@ def complete(self, tab: bool = False) -> Optional[bool]: self.completers, cursor_offset=self.cursor_offset, line=self.current_line, - locals_=cast(Dict[str, Any], self.interp.locals), + locals_=cast(dict[str, Any], self.interp.locals), argspec=self.funcprops, current_block="\n".join(self.buffer + [self.current_line]), complete_magic_methods=self.config.complete_magic_methods, @@ -819,7 +817,7 @@ def complete(self, tab: bool = False) -> Optional[bool]: def format_docstring( self, docstring: str, width: int, height: int - ) -> List[str]: + ) -> list[str]: """Take a string and try to format it into a sane list of strings to be put into the suggestion box.""" @@ -1088,7 +1086,7 @@ def flush(self) -> None: def close(self): """See the flush() method docstring.""" - def tokenize(self, s, newline=False) -> List[Tuple[_TokenType, str]]: + def tokenize(self, s, newline=False) -> list[tuple[_TokenType, str]]: """Tokenizes a line of code, returning pygments tokens with side effects/impurities: - reads self.cpos to see what parens should be highlighted @@ -1105,7 +1103,7 @@ def tokenize(self, s, newline=False) -> List[Tuple[_TokenType, str]]: cursor = len(source) - self.cpos if self.cpos: cursor += 1 - stack: List[Any] = list() + stack: list[Any] = list() all_tokens = list(Python3Lexer().get_tokens(source)) # Unfortunately, Pygments adds a trailing newline and strings with # no size, so strip them @@ -1114,8 +1112,8 @@ def tokenize(self, s, newline=False) -> List[Tuple[_TokenType, str]]: all_tokens[-1] = (all_tokens[-1][0], all_tokens[-1][1].rstrip("\n")) line = pos = 0 parens = dict(zip("{([", "})]")) - line_tokens: List[Tuple[_TokenType, str]] = list() - saved_tokens: List[Tuple[_TokenType, str]] = list() + line_tokens: list[tuple[_TokenType, str]] = list() + saved_tokens: list[tuple[_TokenType, str]] = list() search_for_paren = True for token, value in split_lines(all_tokens): pos += len(value) @@ -1298,7 +1296,7 @@ def token_is_any_of(token): return token_is_any_of -def extract_exit_value(args: Tuple[Any, ...]) -> Any: +def extract_exit_value(args: tuple[Any, ...]) -> Any: """Given the arguments passed to `SystemExit`, return the value that should be passed to `sys.exit`. """ diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 3f334af4a..893539ea7 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -42,7 +42,7 @@ class EvaluationError(Exception): """Raised if an exception occurred in safe_eval.""" -def safe_eval(expr: str, namespace: Dict[str, Any]) -> Any: +def safe_eval(expr: str, namespace: dict[str, Any]) -> Any: """Not all that safe, just catches some errors""" try: return eval(expr, namespace) @@ -199,7 +199,7 @@ def find_attribute_with_name(node, name): def evaluate_current_expression( - cursor_offset: int, line: str, namespace: Optional[Dict[str, Any]] = None + cursor_offset: int, line: str, namespace: Optional[dict[str, Any]] = None ) -> Any: """ Return evaluated expression to the right of the dot of current attribute. diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 5a19c6abb..59102f9e1 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -435,7 +435,7 @@ def setUp(self): self.repl = create_repl() def write_startup_file(self, fname, encoding): - with open(fname, mode="wt", encoding=encoding) as f: + with open(fname, mode="w", encoding=encoding) as f: f.write("# coding: ") f.write(encoding) f.write("\n") diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 3f04222de..5089f3048 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -162,7 +162,7 @@ def fun(number, lst=[]): """ return lst + [number] - def fun_annotations(number: int, lst: List[int] = []) -> List[int]: + def fun_annotations(number: int, lst: list[int] = []) -> list[int]: """ Return a list of numbers @@ -185,7 +185,7 @@ def fun_annotations(number: int, lst: List[int] = []) -> List[int]: def test_issue_966_class_method(self): class Issue966(Sequence): @classmethod - def cmethod(cls, number: int, lst: List[int] = []): + def cmethod(cls, number: int, lst: list[int] = []): """ Return a list of numbers @@ -222,7 +222,7 @@ def bmethod(cls, number, lst): def test_issue_966_static_method(self): class Issue966(Sequence): @staticmethod - def cmethod(number: int, lst: List[int] = []): + def cmethod(number: int, lst: list[int] = []): """ Return a list of numbers diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 967ecbe01..5beb000bd 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -27,7 +27,7 @@ def cursor(s): return cursor_offset, line -def decode(s: str) -> Tuple[Tuple[int, str], Optional[LinePart]]: +def decode(s: str) -> tuple[tuple[int, str], Optional[LinePart]]: """'ad' -> ((3, 'abcd'), (1, 3, 'bdc'))""" if not s.count("|") == 1: diff --git a/bpython/test/test_repl.py b/bpython/test/test_repl.py index 5cafec946..a32ef90e8 100644 --- a/bpython/test/test_repl.py +++ b/bpython/test/test_repl.py @@ -60,7 +60,7 @@ def getstdout(self) -> str: raise NotImplementedError def reprint_line( - self, lineno: int, tokens: List[Tuple[repl._TokenType, str]] + self, lineno: int, tokens: list[tuple[repl._TokenType, str]] ) -> None: raise NotImplementedError diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index 0cb4c01f6..7d82dc7ce 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -18,7 +18,7 @@ def ngettext(singular, plural, n): def init( - locale_dir: Optional[str] = None, languages: Optional[List[str]] = None + locale_dir: Optional[str] = None, languages: Optional[list[str]] = None ) -> None: try: locale.setlocale(locale.LC_ALL, "") From 3167897c7b1a81596f24cfa142708046df1027a3 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 17 Jan 2025 21:48:35 +0100 Subject: [PATCH 1613/1650] Remove pre-3.9 fallback code --- bpython/lazyre.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/bpython/lazyre.py b/bpython/lazyre.py index d397f05cf..a63bb4646 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -21,14 +21,10 @@ # THE SOFTWARE. import re -from typing import Optional, Optional from collections.abc import Iterator +from functools import cached_property from re import Pattern, Match - -try: - from functools import cached_property -except ImportError: - from backports.cached_property import cached_property # type: ignore [no-redef] +from typing import Optional, Optional class LazyReCompile: From 12a65e8b57d39123d264e2217cb0551d43a93dc4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 17 Jan 2025 21:52:23 +0100 Subject: [PATCH 1614/1650] Fix call to preprocess --- bpython/test/test_preprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_preprocess.py b/bpython/test/test_preprocess.py index a72a64b63..8e8a36304 100644 --- a/bpython/test/test_preprocess.py +++ b/bpython/test/test_preprocess.py @@ -12,7 +12,7 @@ from bpython.test.fodder import original, processed -preproc = partial(preprocess, compiler=CommandCompiler) +preproc = partial(preprocess, compiler=CommandCompiler()) def get_fodder_source(test_name): From 1a919d3716b87a183006f73d47d117bc3337a522 Mon Sep 17 00:00:00 2001 From: Jochen Kupperschmidt Date: Wed, 29 Jan 2025 00:22:43 +0100 Subject: [PATCH 1615/1650] Add short project description Should be picked up by PyPI and others. --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index f8b7c3258..b3cb9a4c7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,6 @@ [metadata] name = bpython +description = A fancy curses interface to the Python interactive interpreter long_description = file: README.rst long_description_content_type = text/x-rst license = MIT From b99562370fd0ff4f5b9c2101a39263f7075252d6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Jun 2025 16:39:55 +0200 Subject: [PATCH 1616/1650] CI: no longer test with Python 3.9 --- .github/workflows/build.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a6e9aef0a..cb6d64ad2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -15,12 +15,11 @@ jobs: fail-fast: false matrix: python-version: - - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" - - "pypy-3.9" + - "pypy-3.10" steps: - uses: actions/checkout@v4 with: From 982bd7e20e584220f1c21805a21c345bbf893e12 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Jun 2025 16:40:59 +0200 Subject: [PATCH 1617/1650] Upgrade to Python 3.10+ --- bpython/args.py | 7 ++- bpython/autocomplete.py | 78 ++++++++++++------------- bpython/curtsies.py | 29 +++++---- bpython/curtsiesfrontend/_internal.py | 6 +- bpython/curtsiesfrontend/filewatch.py | 4 +- bpython/curtsiesfrontend/interpreter.py | 6 +- bpython/curtsiesfrontend/parse.py | 3 +- bpython/curtsiesfrontend/repl.py | 40 ++++++------- bpython/filelock.py | 8 +-- bpython/history.py | 8 +-- bpython/importcompletion.py | 8 +-- bpython/inspection.py | 22 +++---- bpython/lazyre.py | 4 +- bpython/line.py | 34 +++++------ bpython/paste.py | 2 +- bpython/patch_linecache.py | 4 +- bpython/repl.py | 47 ++++++++------- bpython/simpleeval.py | 2 +- bpython/test/test_inspection.py | 4 +- bpython/test/test_line_properties.py | 4 +- bpython/translations/__init__.py | 2 +- bpython/urwid.py | 2 +- pyproject.toml | 2 +- 23 files changed, 157 insertions(+), 169 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 1eb59a691..35fd3e7bf 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -36,7 +36,8 @@ import os import sys from pathlib import Path -from typing import Tuple, List, Optional, Callable +from typing import Tuple, List, Optional +from collections.abc import Callable from types import ModuleType from . import __version__, __copyright__ @@ -77,8 +78,8 @@ def log_version(module: ModuleType, name: str) -> None: def parse( - args: Optional[list[str]], - extras: Optional[Options] = None, + args: list[str] | None, + extras: Options | None = None, ignore_stdin: bool = False, ) -> tuple[Config, argparse.Namespace, list[str]]: """Receive an argument list - if None, use sys.argv - parse all args and diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 88afbe54f..4fb62f720 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -236,7 +236,7 @@ def __init__( @abc.abstractmethod def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[set[str]]: + ) -> set[str] | None: """Returns a list of possible matches given a line and cursor, or None if this completion type isn't applicable. @@ -255,7 +255,7 @@ def matches( raise NotImplementedError @abc.abstractmethod - def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + def locate(self, cursor_offset: int, line: str) -> LinePart | None: """Returns a Linepart namedtuple instance or None given cursor and line A Linepart namedtuple contains a start, stop, and word. None is @@ -299,7 +299,7 @@ def __init__( super().__init__(True, mode) - def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + def locate(self, cursor_offset: int, line: str) -> LinePart | None: for completer in self._completers: return_value = completer.locate(cursor_offset, line) if return_value is not None: @@ -311,7 +311,7 @@ def format(self, word: str) -> str: def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[set[str]]: + ) -> set[str] | None: return_value = None all_matches = set() for completer in self._completers: @@ -336,10 +336,10 @@ def __init__( def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[set[str]]: + ) -> set[str] | None: return self.module_gatherer.complete(cursor_offset, line) - def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + def locate(self, cursor_offset: int, line: str) -> LinePart | None: return lineparts.current_word(cursor_offset, line) def format(self, word: str) -> str: @@ -356,7 +356,7 @@ def __init__(self, mode: AutocompleteModes = AutocompleteModes.SIMPLE): def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[set[str]]: + ) -> set[str] | None: cs = lineparts.current_string(cursor_offset, line) if cs is None: return None @@ -371,7 +371,7 @@ def matches( matches.add(filename) return matches - def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + def locate(self, cursor_offset: int, line: str) -> LinePart | None: return lineparts.current_string(cursor_offset, line) def format(self, filename: str) -> str: @@ -389,9 +389,9 @@ def matches( cursor_offset: int, line: str, *, - locals_: Optional[dict[str, Any]] = None, + locals_: dict[str, Any] | None = None, **kwargs: Any, - ) -> Optional[set[str]]: + ) -> set[str] | None: r = self.locate(cursor_offset, line) if r is None: return None @@ -414,7 +414,7 @@ def matches( if _few_enough_underscores(r.word.split(".")[-1], m.split(".")[-1]) } - def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + def locate(self, cursor_offset: int, line: str) -> LinePart | None: return lineparts.current_dotted_attribute(cursor_offset, line) def format(self, word: str) -> str: @@ -474,9 +474,9 @@ def matches( cursor_offset: int, line: str, *, - locals_: Optional[dict[str, Any]] = None, + locals_: dict[str, Any] | None = None, **kwargs: Any, - ) -> Optional[set[str]]: + ) -> set[str] | None: if locals_ is None: return None @@ -500,7 +500,7 @@ def matches( else: return None - def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + def locate(self, cursor_offset: int, line: str) -> LinePart | None: return lineparts.current_dict_key(cursor_offset, line) def format(self, match: str) -> str: @@ -513,10 +513,10 @@ def matches( cursor_offset: int, line: str, *, - current_block: Optional[str] = None, - complete_magic_methods: Optional[bool] = None, + current_block: str | None = None, + complete_magic_methods: bool | None = None, **kwargs: Any, - ) -> Optional[set[str]]: + ) -> set[str] | None: if ( current_block is None or complete_magic_methods is None @@ -531,7 +531,7 @@ def matches( return None return {name for name in MAGIC_METHODS if name.startswith(r.word)} - def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + def locate(self, cursor_offset: int, line: str) -> LinePart | None: return lineparts.current_method_definition_name(cursor_offset, line) @@ -541,9 +541,9 @@ def matches( cursor_offset: int, line: str, *, - locals_: Optional[dict[str, Any]] = None, + locals_: dict[str, Any] | None = None, **kwargs: Any, - ) -> Optional[set[str]]: + ) -> set[str] | None: """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defined in self.namespace that match. @@ -571,7 +571,7 @@ def matches( matches.add(_callable_postfix(val, word)) return matches if matches else None - def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + def locate(self, cursor_offset: int, line: str) -> LinePart | None: return lineparts.current_single_word(cursor_offset, line) @@ -581,9 +581,9 @@ def matches( cursor_offset: int, line: str, *, - funcprops: Optional[inspection.FuncProps] = None, + funcprops: inspection.FuncProps | None = None, **kwargs: Any, - ) -> Optional[set[str]]: + ) -> set[str] | None: if funcprops is None: return None @@ -603,7 +603,7 @@ def matches( ) return matches if matches else None - def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + def locate(self, cursor_offset: int, line: str) -> LinePart | None: r = lineparts.current_word(cursor_offset, line) if r and r.word[-1] == "(": # if the word ends with a (, it's the parent word with an empty @@ -614,7 +614,7 @@ def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: class ExpressionAttributeCompletion(AttrCompletion): # could replace attr completion as a more general case with some work - def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + def locate(self, cursor_offset: int, line: str) -> LinePart | None: return lineparts.current_expression_attribute(cursor_offset, line) def matches( @@ -622,9 +622,9 @@ def matches( cursor_offset: int, line: str, *, - locals_: Optional[dict[str, Any]] = None, + locals_: dict[str, Any] | None = None, **kwargs: Any, - ) -> Optional[set[str]]: + ) -> set[str] | None: if locals_ is None: locals_ = __main__.__dict__ @@ -648,26 +648,26 @@ def matches( class MultilineJediCompletion(BaseCompletionType): # type: ignore [no-redef] def matches( self, cursor_offset: int, line: str, **kwargs: Any - ) -> Optional[set[str]]: + ) -> set[str] | None: return None - def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]: + def locate(self, cursor_offset: int, line: str) -> LinePart | None: return None else: class MultilineJediCompletion(BaseCompletionType): # type: ignore [no-redef] - _orig_start: Optional[int] + _orig_start: int | None def matches( self, cursor_offset: int, line: str, *, - current_block: Optional[str] = None, - history: Optional[list[str]] = None, + current_block: str | None = None, + history: list[str] | None = None, **kwargs: Any, - ) -> Optional[set[str]]: + ) -> set[str] | None: if ( current_block is None or history is None @@ -725,12 +725,12 @@ def get_completer( cursor_offset: int, line: str, *, - locals_: Optional[dict[str, Any]] = None, - argspec: Optional[inspection.FuncProps] = None, - history: Optional[list[str]] = None, - current_block: Optional[str] = None, - complete_magic_methods: Optional[bool] = None, -) -> tuple[list[str], Optional[BaseCompletionType]]: + locals_: dict[str, Any] | None = None, + argspec: inspection.FuncProps | None = None, + history: list[str] | None = None, + current_block: str | None = None, + complete_magic_methods: bool | None = None, +) -> tuple[list[str], BaseCompletionType | None]: """Returns a list of matches and an applicable completer If no matches available, returns a tuple of an empty list and None diff --git a/bpython/curtsies.py b/bpython/curtsies.py index 547a853ee..b57e47a9d 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -23,7 +23,6 @@ from typing import ( Any, - Callable, Dict, List, Optional, @@ -31,28 +30,28 @@ Tuple, Union, ) -from collections.abc import Generator, Sequence +from collections.abc import Callable, Generator, Sequence logger = logging.getLogger(__name__) class SupportsEventGeneration(Protocol): def send( - self, timeout: Optional[float] - ) -> Union[str, curtsies.events.Event, None]: ... + self, timeout: float | None + ) -> str | curtsies.events.Event | None: ... def __iter__(self) -> "SupportsEventGeneration": ... - def __next__(self) -> Union[str, curtsies.events.Event, None]: ... + def __next__(self) -> str | curtsies.events.Event | None: ... class FullCurtsiesRepl(BaseRepl): def __init__( self, config: Config, - locals_: Optional[dict[str, Any]] = None, - banner: Optional[str] = None, - interp: Optional[Interp] = None, + locals_: dict[str, Any] | None = None, + banner: str | None = None, + interp: Interp | None = None, ) -> None: self.input_generator = curtsies.input.Input( keynames="curtsies", sigint_event=True, paste_threshold=None @@ -129,7 +128,7 @@ def after_suspend(self) -> None: self.interrupting_refresh() def process_event_and_paint( - self, e: Union[str, curtsies.events.Event, None] + self, e: str | curtsies.events.Event | None ) -> None: """If None is passed in, just paint the screen""" try: @@ -151,7 +150,7 @@ def process_event_and_paint( def mainloop( self, interactive: bool = True, - paste: Optional[curtsies.events.PasteEvent] = None, + paste: curtsies.events.PasteEvent | None = None, ) -> None: if interactive: # Add custom help command @@ -178,10 +177,10 @@ def mainloop( def main( - args: Optional[list[str]] = None, - locals_: Optional[dict[str, Any]] = None, - banner: Optional[str] = None, - welcome_message: Optional[str] = None, + args: list[str] | None = None, + locals_: dict[str, Any] | None = None, + banner: str | None = None, + welcome_message: str | None = None, ) -> Any: """ banner is displayed directly after the version information. @@ -249,7 +248,7 @@ def curtsies_arguments(parser: argparse._ArgumentGroup) -> None: def _combined_events( event_provider: SupportsEventGeneration, paste_threshold: int -) -> Generator[Union[str, curtsies.events.Event, None], Optional[float], None]: +) -> Generator[str | curtsies.events.Event | None, float | None, None]: """Combines consecutive keypress events into paste events.""" timeout = yield "nonsense_event" # so send can be used immediately queue: collections.deque = collections.deque() diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index cb7b81057..8c070b34c 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -34,9 +34,9 @@ def __enter__(self): def __exit__( self, - exc_type: Optional[type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> Literal[False]: pydoc.pager = self._orig_pager return False diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 53ae47844..2822db6df 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -1,7 +1,7 @@ import os from collections import defaultdict -from typing import Callable, Dict, Set, List -from collections.abc import Iterable, Sequence +from typing import Dict, Set, List +from collections.abc import Callable, Iterable, Sequence from .. import importcompletion diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 6532d9688..280c56ede 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -49,7 +49,7 @@ class BPythonFormatter(Formatter): def __init__( self, color_scheme: dict[_TokenType, str], - **options: Union[str, bool, None], + **options: str | bool | None, ) -> None: self.f_strings = {k: f"\x01{v}" for k, v in color_scheme.items()} # FIXME: mypy currently fails to handle this properly @@ -68,7 +68,7 @@ def format(self, tokensource, outfile): class Interp(ReplInterpreter): def __init__( self, - locals: Optional[dict[str, Any]] = None, + locals: dict[str, Any] | None = None, ) -> None: """Constructor. @@ -79,7 +79,7 @@ def __init__( # typically changed after being instantiated # but used when interpreter used corresponding REPL - def write(err_line: Union[str, FmtStr]) -> None: + def write(err_line: str | FmtStr) -> None: """Default stderr handler for tracebacks Accepts FmtStrs so interpreters can output them""" diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 96e91e55e..28b32e649 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -1,6 +1,7 @@ import re from functools import partial -from typing import Any, Callable, Dict, Tuple +from typing import Any, Dict, Tuple +from collections.abc import Callable from curtsies.formatstring import fmtstr, FmtStr from curtsies.termformatconstants import ( diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 09f73a82f..2a304312d 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -112,7 +112,7 @@ def __init__( self, coderunner: CodeRunner, repl: "BaseRepl", - configured_edit_keys: Optional[AbstractEdits] = None, + configured_edit_keys: AbstractEdits | None = None, ): self.coderunner = coderunner self.repl = repl @@ -126,7 +126,7 @@ def __init__( else: self.rl_char_sequences = edit_keys - def process_event(self, e: Union[events.Event, str]) -> None: + def process_event(self, e: events.Event | str) -> None: assert self.has_focus logger.debug("fake input processing event %r", e) @@ -194,7 +194,7 @@ def readline(self, size: int = -1) -> str: self.readline_results.append(value) return value if size <= -1 else value[:size] - def readlines(self, size: Optional[int] = -1) -> list[str]: + def readlines(self, size: int | None = -1) -> list[str]: if size is None: # the default readlines implementation also accepts None size = -1 @@ -337,10 +337,10 @@ def __init__( self, config: Config, window: CursorAwareWindow, - locals_: Optional[dict[str, Any]] = None, - banner: Optional[str] = None, - interp: Optional[Interp] = None, - orig_tcattrs: Optional[list[Any]] = None, + locals_: dict[str, Any] | None = None, + banner: str | None = None, + interp: Interp | None = None, + orig_tcattrs: list[Any] | None = None, ): """ locals_ is a mapping of locals to pass into the interpreter @@ -398,7 +398,7 @@ def __init__( self._current_line = "" # current line of output - stdout and stdin go here - self.current_stdouterr_line: Union[str, FmtStr] = "" + self.current_stdouterr_line: str | FmtStr = "" # this is every line that's been displayed (input and output) # as with formatting applied. Logical lines that exceeded the terminal width @@ -427,7 +427,7 @@ def __init__( # cursor position relative to start of current_line, 0 is first char self._cursor_offset = 0 - self.orig_tcattrs: Optional[list[Any]] = orig_tcattrs + self.orig_tcattrs: list[Any] | None = orig_tcattrs self.coderunner = CodeRunner(self.interp, self.request_refresh) @@ -459,7 +459,7 @@ def __init__( # some commands act differently based on the prev event # this list doesn't include instances of event.Event, # only keypress-type events (no refresh screen events etc.) - self.last_events: list[Optional[str]] = [None] * 50 + self.last_events: list[str | None] = [None] * 50 # displays prev events in a column on the right hand side self.presentation_mode = False @@ -600,9 +600,9 @@ def __enter__(self): def __exit__( self, - exc_type: Optional[type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> Literal[False]: sys.stdin = self.orig_stdin sys.stdout = self.orig_stdout @@ -616,7 +616,7 @@ def __exit__( sys.meta_path = self.orig_meta_path return False - def sigwinch_handler(self, signum: int, frame: Optional[FrameType]) -> None: + def sigwinch_handler(self, signum: int, frame: FrameType | None) -> None: old_rows, old_columns = self.height, self.width self.height, self.width = self.get_term_hw() cursor_dy = self.get_cursor_vertical_diff() @@ -632,7 +632,7 @@ def sigwinch_handler(self, signum: int, frame: Optional[FrameType]) -> None: self.scroll_offset, ) - def sigtstp_handler(self, signum: int, frame: Optional[FrameType]) -> None: + def sigtstp_handler(self, signum: int, frame: FrameType | None) -> None: self.scroll_offset = len(self.lines_for_display) self.__exit__(None, None, None) self.on_suspend() @@ -647,7 +647,7 @@ def clean_up_current_line_for_exit(self): self.unhighlight_paren() # Event handling - def process_event(self, e: Union[events.Event, str]) -> Optional[bool]: + def process_event(self, e: events.Event | str) -> bool | None: """Returns True if shutting down, otherwise returns None. Mostly mutates state of Repl object""" @@ -660,7 +660,7 @@ def process_event(self, e: Union[events.Event, str]) -> Optional[bool]: self.process_key_event(e) return None - def process_control_event(self, e: events.Event) -> Optional[bool]: + def process_control_event(self, e: events.Event) -> bool | None: if isinstance(e, bpythonevents.ScheduledRefreshRequestEvent): # This is a scheduled refresh - it's really just a refresh (so nop) pass @@ -2234,9 +2234,7 @@ def compress_paste_event(paste_event): return None -def just_simple_events( - event_list: Iterable[Union[str, events.Event]] -) -> list[str]: +def just_simple_events(event_list: Iterable[str | events.Event]) -> list[str]: simple_events = [] for e in event_list: if isinstance(e, events.Event): @@ -2253,7 +2251,7 @@ def just_simple_events( return simple_events -def is_simple_event(e: Union[str, events.Event]) -> bool: +def is_simple_event(e: str | events.Event) -> bool: if isinstance(e, events.Event): return False return ( diff --git a/bpython/filelock.py b/bpython/filelock.py index 5ed8769fd..b8eb11ff2 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -56,9 +56,9 @@ def __enter__(self) -> "BaseLock": def __exit__( self, - exc_type: Optional[type[BaseException]], - exc: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, ) -> Literal[False]: if self.locked: self.release() @@ -122,7 +122,7 @@ def release(self) -> None: def FileLock( - fileobj: IO, mode: int = 0, filename: Optional[str] = None + fileobj: IO, mode: int = 0, filename: str | None = None ) -> BaseLock: if has_fcntl: return UnixFileLock(fileobj, mode) diff --git a/bpython/history.py b/bpython/history.py index 386214b44..b58309b53 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -37,7 +37,7 @@ class History: def __init__( self, - entries: Optional[Iterable[str]] = None, + entries: Iterable[str] | None = None, duplicates: bool = True, hist_size: int = 100, ) -> None: @@ -78,7 +78,7 @@ def back( self, start: bool = True, search: bool = False, - target: Optional[str] = None, + target: str | None = None, include_current: bool = False, ) -> str: """Move one step back in the history.""" @@ -128,7 +128,7 @@ def forward( self, start: bool = True, search: bool = False, - target: Optional[str] = None, + target: str | None = None, include_current: bool = False, ) -> str: """Move one step forward in the history.""" @@ -214,7 +214,7 @@ def save(self, filename: Path, encoding: str, lines: int = 0) -> None: self.save_to(hfile, self.entries, lines) def save_to( - self, fd: TextIO, entries: Optional[list[str]] = None, lines: int = 0 + self, fd: TextIO, entries: list[str] | None = None, lines: int = 0 ) -> None: if entries is None: entries = self.entries diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index da1b91405..570996d44 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -63,8 +63,8 @@ class _LoadedInode: class ModuleGatherer: def __init__( self, - paths: Optional[Iterable[Union[str, Path]]] = None, - skiplist: Optional[Sequence[str]] = None, + paths: Iterable[str | Path] | None = None, + skiplist: Sequence[str] | None = None, ) -> None: """Initialize module gatherer with all modules in `paths`, which should be a list of directory names. If `paths` is not given, `sys.path` will be used.""" @@ -131,7 +131,7 @@ def module_attr_matches(self, name: str) -> set[str]: """Only attributes which are modules to replace name with""" return self.attr_matches(name, only_modules=True) - def complete(self, cursor_offset: int, line: str) -> Optional[set[str]]: + def complete(self, cursor_offset: int, line: str) -> set[str] | None: """Construct a full list of possibly completions for imports.""" tokens = line.split() if "from" not in tokens and "import" not in tokens: @@ -167,7 +167,7 @@ def complete(self, cursor_offset: int, line: str) -> Optional[set[str]]: else: return None - def find_modules(self, path: Path) -> Generator[Optional[str], None, None]: + def find_modules(self, path: Path) -> Generator[str | None, None, None]: """Find all modules (and packages) for a given directory.""" if not path.is_dir(): # Perhaps a zip file diff --git a/bpython/inspection.py b/bpython/inspection.py index fb1124ebf..63f0e2d38 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -28,7 +28,6 @@ from dataclasses import dataclass from typing import ( Any, - Callable, Optional, Type, Dict, @@ -36,6 +35,7 @@ ContextManager, Literal, ) +from collections.abc import Callable from types import MemberDescriptorType, TracebackType from pygments.token import Token @@ -63,12 +63,12 @@ def __repr__(self) -> str: @dataclass class ArgSpec: args: list[str] - varargs: Optional[str] - varkwargs: Optional[str] - defaults: Optional[list[_Repr]] + varargs: str | None + varkwargs: str | None + defaults: list[_Repr] | None kwonly: list[str] - kwonly_defaults: Optional[dict[str, _Repr]] - annotations: Optional[dict[str, Any]] + kwonly_defaults: dict[str, _Repr] | None + annotations: dict[str, Any] | None @dataclass @@ -118,9 +118,9 @@ def __enter__(self) -> None: def __exit__( self, - exc_type: Optional[type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> Literal[False]: """Restore an object's magic methods.""" type_ = type(self._obj) @@ -224,7 +224,7 @@ def _fix_default_values(f: Callable, argspec: ArgSpec) -> ArgSpec: ) -def _getpydocspec(f: Callable) -> Optional[ArgSpec]: +def _getpydocspec(f: Callable) -> ArgSpec | None: try: argspec = pydoc.getdoc(f) except NameError: @@ -267,7 +267,7 @@ def _getpydocspec(f: Callable) -> Optional[ArgSpec]: ) -def getfuncprops(func: str, f: Callable) -> Optional[FuncProps]: +def getfuncprops(func: str, f: Callable) -> FuncProps | None: # Check if it's a real bound method or if it's implicitly calling __init__ # (i.e. FooClass(...) and not FooClass.__init__(...) -- the former would # not take 'self', the latter would: diff --git a/bpython/lazyre.py b/bpython/lazyre.py index a63bb4646..1d9036164 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -44,10 +44,10 @@ def compiled(self) -> Pattern[str]: def finditer(self, *args, **kwargs) -> Iterator[Match[str]]: return self.compiled.finditer(*args, **kwargs) - def search(self, *args, **kwargs) -> Optional[Match[str]]: + def search(self, *args, **kwargs) -> Match[str] | None: return self.compiled.search(*args, **kwargs) - def match(self, *args, **kwargs) -> Optional[Match[str]]: + def match(self, *args, **kwargs) -> Match[str] | None: return self.compiled.match(*args, **kwargs) def sub(self, *args, **kwargs) -> str: diff --git a/bpython/line.py b/bpython/line.py index 363419fe0..e64b20d90 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -24,7 +24,7 @@ class LinePart: CHARACTER_PAIR_MAP = {"(": ")", "{": "}", "[": "]", "'": "'", '"': '"'} -def current_word(cursor_offset: int, line: str) -> Optional[LinePart]: +def current_word(cursor_offset: int, line: str) -> LinePart | None: """the object.attribute.attribute just before or under the cursor""" start = cursor_offset end = cursor_offset @@ -76,7 +76,7 @@ def current_word(cursor_offset: int, line: str) -> Optional[LinePart]: ) -def current_dict_key(cursor_offset: int, line: str) -> Optional[LinePart]: +def current_dict_key(cursor_offset: int, line: str) -> LinePart | None: """If in dictionary completion, return the current key""" for m in _current_dict_key_re.finditer(line): if m.start(1) <= cursor_offset <= m.end(1): @@ -96,7 +96,7 @@ def current_dict_key(cursor_offset: int, line: str) -> Optional[LinePart]: ) -def current_dict(cursor_offset: int, line: str) -> Optional[LinePart]: +def current_dict(cursor_offset: int, line: str) -> LinePart | None: """If in dictionary completion, return the dict that should be used""" for m in _current_dict_re.finditer(line): if m.start(2) <= cursor_offset <= m.end(2): @@ -110,7 +110,7 @@ def current_dict(cursor_offset: int, line: str) -> Optional[LinePart]: ) -def current_string(cursor_offset: int, line: str) -> Optional[LinePart]: +def current_string(cursor_offset: int, line: str) -> LinePart | None: """If inside a string of nonzero length, return the string (excluding quotes) @@ -126,7 +126,7 @@ def current_string(cursor_offset: int, line: str) -> Optional[LinePart]: _current_object_re = LazyReCompile(r"([\w_][\w0-9_]*)[.]") -def current_object(cursor_offset: int, line: str) -> Optional[LinePart]: +def current_object(cursor_offset: int, line: str) -> LinePart | None: """If in attribute completion, the object on which attribute should be looked up.""" match = current_word(cursor_offset, line) @@ -145,9 +145,7 @@ def current_object(cursor_offset: int, line: str) -> Optional[LinePart]: _current_object_attribute_re = LazyReCompile(r"([\w_][\w0-9_]*)[.]?") -def current_object_attribute( - cursor_offset: int, line: str -) -> Optional[LinePart]: +def current_object_attribute(cursor_offset: int, line: str) -> LinePart | None: """If in attribute completion, the attribute being completed""" # TODO replace with more general current_expression_attribute match = current_word(cursor_offset, line) @@ -168,9 +166,7 @@ def current_object_attribute( ) -def current_from_import_from( - cursor_offset: int, line: str -) -> Optional[LinePart]: +def current_from_import_from(cursor_offset: int, line: str) -> LinePart | None: """If in from import completion, the word after from returns None if cursor not in or just after one of the two interesting @@ -194,7 +190,7 @@ def current_from_import_from( def current_from_import_import( cursor_offset: int, line: str -) -> Optional[LinePart]: +) -> LinePart | None: """If in from import completion, the word after import being completed returns None if cursor not in or just after one of these words @@ -221,7 +217,7 @@ def current_from_import_import( _current_import_re_3 = LazyReCompile(r"[,][ ]*([\w0-9_.]*)") -def current_import(cursor_offset: int, line: str) -> Optional[LinePart]: +def current_import(cursor_offset: int, line: str) -> LinePart | None: # TODO allow for multiple as's baseline = _current_import_re_1.search(line) if baseline is None: @@ -244,7 +240,7 @@ def current_import(cursor_offset: int, line: str) -> Optional[LinePart]: def current_method_definition_name( cursor_offset: int, line: str -) -> Optional[LinePart]: +) -> LinePart | None: """The name of a method being defined""" for m in _current_method_definition_name_re.finditer(line): if m.start(1) <= cursor_offset <= m.end(1): @@ -255,7 +251,7 @@ def current_method_definition_name( _current_single_word_re = LazyReCompile(r"(? Optional[LinePart]: +def current_single_word(cursor_offset: int, line: str) -> LinePart | None: """the un-dotted word just before or under the cursor""" for m in _current_single_word_re.finditer(line): if m.start(1) <= cursor_offset <= m.end(1): @@ -263,9 +259,7 @@ def current_single_word(cursor_offset: int, line: str) -> Optional[LinePart]: return None -def current_dotted_attribute( - cursor_offset: int, line: str -) -> Optional[LinePart]: +def current_dotted_attribute(cursor_offset: int, line: str) -> LinePart | None: """The dotted attribute-object pair before the cursor""" match = current_word(cursor_offset, line) if match is not None and "." in match.word[1:]: @@ -280,7 +274,7 @@ def current_dotted_attribute( def current_expression_attribute( cursor_offset: int, line: str -) -> Optional[LinePart]: +) -> LinePart | None: """If after a dot, the attribute being completed""" # TODO replace with more general current_expression_attribute for m in _current_expression_attribute_re.finditer(line): @@ -290,7 +284,7 @@ def current_expression_attribute( def cursor_on_closing_char_pair( - cursor_offset: int, line: str, ch: Optional[str] = None + cursor_offset: int, line: str, ch: str | None = None ) -> tuple[bool, bool]: """Checks if cursor sits on closing character of a pair and whether its pair character is directly behind it diff --git a/bpython/paste.py b/bpython/paste.py index e846aba3f..8ca6f2df5 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -37,7 +37,7 @@ class PasteFailed(Exception): class Paster(Protocol): - def paste(self, s: str) -> tuple[str, Optional[str]]: ... + def paste(self, s: str) -> tuple[str, str | None]: ... class PastePinnwand: diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index 5bf4a45b8..68787e709 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -8,9 +8,7 @@ class BPythonLinecache(dict): def __init__( self, - bpython_history: Optional[ - list[tuple[int, None, list[str], str]] - ] = None, + bpython_history: None | (list[tuple[int, None, list[str], str]]) = None, *args, **kwargs, ) -> None: diff --git a/bpython/repl.py b/bpython/repl.py index de8890310..50da2d462 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -41,7 +41,6 @@ from types import ModuleType, TracebackType from typing import ( Any, - Callable, Dict, List, Literal, @@ -52,7 +51,7 @@ Union, cast, ) -from collections.abc import Iterable +from collections.abc import Callable, Iterable from pygments.lexers import Python3Lexer from pygments.token import Token, _TokenType @@ -85,9 +84,9 @@ def __enter__(self) -> None: def __exit__( self, - exc_type: Optional[type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> Literal[False]: self.last_command = time.monotonic() - self.start self.running_time += self.last_command @@ -108,7 +107,7 @@ class Interpreter(code.InteractiveInterpreter): def __init__( self, - locals: Optional[dict[str, Any]] = None, + locals: dict[str, Any] | None = None, ) -> None: """Constructor. @@ -125,7 +124,7 @@ def __init__( traceback. """ - self.syntaxerror_callback: Optional[Callable] = None + self.syntaxerror_callback: Callable | None = None if locals is None: # instead of messing with sys.modules, we should modify sys.modules @@ -139,7 +138,7 @@ def __init__( def runsource( self, source: str, - filename: Optional[str] = None, + filename: str | None = None, symbol: str = "single", ) -> bool: """Execute Python code. @@ -152,7 +151,7 @@ def runsource( with self.timer: return super().runsource(source, filename, symbol) - def showsyntaxerror(self, filename: Optional[str] = None, **kwargs) -> None: + def showsyntaxerror(self, filename: str | None = None, **kwargs) -> None: """Override the regular handler, the code's copied and pasted from code.py, as per showtraceback, but with the syntaxerror callback called and the text in a pretty colour.""" @@ -227,9 +226,9 @@ def __init__(self) -> None: # original line (before match replacements) self.orig_line = "" # class describing the current type of completion - self.completer: Optional[autocomplete.BaseCompletionType] = None - self.start: Optional[int] = None - self.end: Optional[int] = None + self.completer: autocomplete.BaseCompletionType | None = None + self.start: int | None = None + self.end: int | None = None def __nonzero__(self) -> bool: """MatchesIterator is False when word hasn't been replaced yet""" @@ -352,7 +351,7 @@ def notify( pass @abc.abstractmethod - def file_prompt(self, s: str) -> Optional[str]: + def file_prompt(self, s: str) -> str | None: pass @@ -368,7 +367,7 @@ def notify( ) -> None: pass - def file_prompt(self, s: str) -> Optional[str]: + def file_prompt(self, s: str) -> str | None: return None @@ -384,7 +383,7 @@ class _FuncExpr: function_expr: str arg_number: int opening: str - keyword: Optional[str] = None + keyword: str | None = None class Repl(metaclass=abc.ABCMeta): @@ -493,11 +492,11 @@ def __init__(self, interp: Interpreter, config: Config): self.evaluating = False self.matches_iter = MatchesIterator() self.funcprops = None - self.arg_pos: Union[str, int, None] = None + self.arg_pos: str | int | None = None self.current_func = None - self.highlighted_paren: Optional[ + self.highlighted_paren: None | ( tuple[Any, list[tuple[_TokenType, str]]] - ] = None + ) = None self._C: dict[str, int] = {} self.prev_block_finished: int = 0 self.interact: Interaction = NoInteraction(self.config) @@ -509,7 +508,7 @@ def __init__(self, interp: Interpreter, config: Config): # Necessary to fix mercurial.ui.ui expecting sys.stderr to have this # attribute self.closed = False - self.paster: Union[PasteHelper, PastePinnwand] + self.paster: PasteHelper | PastePinnwand if self.config.hist_file.exists(): try: @@ -595,7 +594,7 @@ def get_object(self, name: str) -> Any: @classmethod def _funcname_and_argnum( cls, line: str - ) -> tuple[Optional[str], Optional[Union[str, int]]]: + ) -> tuple[str | None, str | int | None]: """Parse out the current function name and arg from a line of code.""" # each element in stack is a _FuncExpr instance # if keyword is not None, we've encountered a keyword and so we're done counting @@ -715,7 +714,7 @@ def get_source_of_current_name(self) -> str: current name in the current input line. Throw `SourceNotFound` if the source cannot be found.""" - obj: Optional[Callable] = self.current_func + obj: Callable | None = self.current_func try: if obj is None: line = self.current_line @@ -761,7 +760,7 @@ def set_docstring(self) -> None: # If exactly one match that is equal to current line, clear matches # If example one match and tab=True, then choose that and clear matches - def complete(self, tab: bool = False) -> Optional[bool]: + def complete(self, tab: bool = False) -> bool | None: """Construct a full list of possible completions and display them in a window. Also check if there's an available argspec (via the inspect module) and bang that on top of the completions too. @@ -937,7 +936,7 @@ def copy2clipboard(self) -> None: else: self.interact.notify(_("Copied content to clipboard.")) - def pastebin(self, s=None) -> Optional[str]: + def pastebin(self, s=None) -> str | None: """Upload to a pastebin and display the URL in the status bar.""" if s is None: @@ -951,7 +950,7 @@ def pastebin(self, s=None) -> Optional[str]: else: return self.do_pastebin(s) - def do_pastebin(self, s) -> Optional[str]: + def do_pastebin(self, s) -> str | None: """Actually perform the upload.""" paste_url: str if s == self.prev_pastebin_content: diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 893539ea7..1e26ded4c 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -199,7 +199,7 @@ def find_attribute_with_name(node, name): def evaluate_current_expression( - cursor_offset: int, line: str, namespace: Optional[dict[str, Any]] = None + cursor_offset: int, line: str, namespace: dict[str, Any] | None = None ) -> Any: """ Return evaluated expression to the right of the dot of current attribute. diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index 5089f3048..c83ca0128 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -103,9 +103,7 @@ def test_get_source_latin1(self): self.assertEqual(inspect.getsource(encoding_latin1.foo), foo_non_ascii) def test_get_source_file(self): - path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "fodder" - ) + path = os.path.join(os.path.dirname(__file__), "fodder") encoding = inspection.get_encoding_file( os.path.join(path, "encoding_ascii.py") diff --git a/bpython/test/test_line_properties.py b/bpython/test/test_line_properties.py index 5beb000bd..017978277 100644 --- a/bpython/test/test_line_properties.py +++ b/bpython/test/test_line_properties.py @@ -27,7 +27,7 @@ def cursor(s): return cursor_offset, line -def decode(s: str) -> tuple[tuple[int, str], Optional[LinePart]]: +def decode(s: str) -> tuple[tuple[int, str], LinePart | None]: """'ad' -> ((3, 'abcd'), (1, 3, 'bdc'))""" if not s.count("|") == 1: @@ -52,7 +52,7 @@ def line_with_cursor(cursor_offset: int, line: str) -> str: return line[:cursor_offset] + "|" + line[cursor_offset:] -def encode(cursor_offset: int, line: str, result: Optional[LinePart]) -> str: +def encode(cursor_offset: int, line: str, result: LinePart | None) -> str: """encode(3, 'abdcd', (1, 3, 'bdc')) -> ad' Written for prettier assert error messages diff --git a/bpython/translations/__init__.py b/bpython/translations/__init__.py index 7d82dc7ce..069f34653 100644 --- a/bpython/translations/__init__.py +++ b/bpython/translations/__init__.py @@ -18,7 +18,7 @@ def ngettext(singular, plural, n): def init( - locale_dir: Optional[str] = None, languages: Optional[list[str]] = None + locale_dir: str | None = None, languages: list[str] | None = None ) -> None: try: locale.setlocale(locale.LC_ALL, "") diff --git a/bpython/urwid.py b/bpython/urwid.py index 3c075d937..d94fc2e77 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -563,7 +563,7 @@ def _prompt_result(self, text): self.callback = None callback(text) - def file_prompt(self, s: str) -> Optional[str]: + def file_prompt(self, s: str) -> str | None: raise NotImplementedError diff --git a/pyproject.toml b/pyproject.toml index ca4e04508..0a891d27c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 80 -target_version = ["py39"] +target_version = ["py310"] include = '\.pyi?$' exclude = ''' /( From 42408f904587c19320f4805eb96ad43bedfc74f0 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 2 Jun 2025 17:15:38 +0200 Subject: [PATCH 1618/1650] Require urwid < 3.0 --- .github/workflows/build.yaml | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cb6d64ad2..e7852728e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -32,7 +32,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install urwid twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" + pip install "urwid < 3.0" twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" pip install pytest pytest-cov numpy - name: Build with Python ${{ matrix.python-version }} run: | diff --git a/setup.cfg b/setup.cfg index b3cb9a4c7..7d61ee1ce 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,7 +35,7 @@ install_requires = [options.extras_require] clipboard = pyperclip jedi = jedi >= 0.16 -urwid = urwid +urwid = urwid < 3.0 watch = watchdog [options.entry_points] From 4eb111d1f4c39b242ecd0968c47e3d9d4db80d96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 06:50:34 +0000 Subject: [PATCH 1619/1650] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 2 +- .github/workflows/lint.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e7852728e..d47929d49 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,7 +21,7 @@ jobs: - "3.13" - "pypy-3.10" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index b60561592..46506b267 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -8,7 +8,7 @@ jobs: black: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 - name: Install dependencies @@ -21,7 +21,7 @@ jobs: codespell: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: codespell-project/actions-codespell@master with: skip: "*.po,encoding_latin1.py" @@ -30,7 +30,7 @@ jobs: mypy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 - name: Install dependencies From 13336953a927ed141d08f388ef40a56f1ac2b5e6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 8 Sep 2025 10:37:56 +0200 Subject: [PATCH 1620/1650] CI: skip codespell of tests --- .github/workflows/lint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 46506b267..ec4c156f5 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v5 - uses: codespell-project/actions-codespell@master with: - skip: "*.po,encoding_latin1.py" + skip: "*.po,encoding_latin1.py,test_repl.py" ignore_words_list: ba,te,deltion,dedent,dedented,assertIn mypy: From c07b6f3e1547bac816c37faeb077c2f4d83cd826 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Sep 2025 07:54:47 +0000 Subject: [PATCH 1621/1650] Bump actions/setup-python from 5 to 6 (#1042) --- .github/workflows/build.yaml | 2 +- .github/workflows/lint.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d47929d49..4372d832c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -25,7 +25,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index ec4c156f5..45d4c5f61 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -32,7 +32,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 - name: Install dependencies run: | python -m pip install --upgrade pip From 3e430e16dd6b3bc735a4e3cb70dd04b2b11f28e4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 24 Sep 2025 09:58:30 +0200 Subject: [PATCH 1622/1650] Convert sys.ps1 to a string to work-around non-str sys.ps1 from vscode (fixes #1041) --- bpython/repl.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bpython/repl.py b/bpython/repl.py index 50da2d462..9779153e3 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -535,11 +535,17 @@ def __init__(self, interp: Interpreter, config: Config): @property def ps1(self) -> str: - return cast(str, getattr(sys, "ps1", ">>> ")) + if hasattr(sys, "ps1"): + # noop in most cases, but at least vscode injects a non-str ps1 + # see #1041 + return str(sys.ps1) + return ">>> " @property def ps2(self) -> str: - return cast(str, getattr(sys, "ps2", "... ")) + if hasattr(sys, "ps2"): + return str(sys.ps2) + return ">>> " def startup(self) -> None: """ From b5f428c2953a0ff107ecfca12f5c75e2ae876b85 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Wed, 24 Sep 2025 10:28:10 +0200 Subject: [PATCH 1623/1650] Unbreak default ps2 --- bpython/repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/repl.py b/bpython/repl.py index 9779153e3..f07494b6e 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -545,7 +545,7 @@ def ps1(self) -> str: def ps2(self) -> str: if hasattr(sys, "ps2"): return str(sys.ps2) - return ">>> " + return "... " def startup(self) -> None: """ From 064ae933ee909e87c3b698a5fdf5c3062c5a318b Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 16:53:47 +0200 Subject: [PATCH 1624/1650] Align simple_eval with Python 3.10+ (fixes #1035) --- bpython/simpleeval.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/bpython/simpleeval.py b/bpython/simpleeval.py index 1e26ded4c..6e911590e 100644 --- a/bpython/simpleeval.py +++ b/bpython/simpleeval.py @@ -26,16 +26,13 @@ """ import ast -import sys import builtins -from typing import Dict, Any, Optional +from typing import Any from . import line as line_properties from .inspection import getattr_safe -_string_type_nodes = (ast.Str, ast.Bytes) _numeric_types = (int, float, complex) -_name_type_nodes = (ast.Name,) class EvaluationError(Exception): @@ -123,7 +120,7 @@ def _convert(node): return list() # this is a deviation from literal_eval: we allow non-literals - elif isinstance(node, _name_type_nodes): + elif isinstance(node, ast.Name): try: return namespace[node.id] except KeyError: @@ -147,7 +144,9 @@ def _convert(node): elif isinstance(node, ast.BinOp) and isinstance( node.op, (ast.Add, ast.Sub) ): - # ast.literal_eval does ast typechecks here, we use type checks + # this is a deviation from literal_eval: ast.literal_eval accepts + # (+/-) int, float and complex literals as left operand, and complex + # as right operation, we evaluate as much as possible left = _convert(node.left) right = _convert(node.right) if not ( From 0a75f527ffd675d9a9a3896d12eb737492db90a4 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 17:09:27 +0200 Subject: [PATCH 1625/1650] Make -q hide the welcome message (fixes #1036) --- bpython/curtsies.py | 6 ++++++ bpython/curtsiesfrontend/repl.py | 15 +++------------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/bpython/curtsies.py b/bpython/curtsies.py index b57e47a9d..ae48a6007 100644 --- a/bpython/curtsies.py +++ b/bpython/curtsies.py @@ -233,6 +233,12 @@ def curtsies_arguments(parser: argparse._ArgumentGroup) -> None: print(bpargs.version_banner()) if banner is not None: print(banner) + if welcome_message is None and not options.quiet and config.help_key: + welcome_message = ( + _("Welcome to bpython!") + + " " + + _("Press <%s> for help.") % config.help_key + ) repl = FullCurtsiesRepl(config, locals_, welcome_message, interp) try: diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 2a304312d..5dd8d988b 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -360,15 +360,6 @@ def __init__( if interp is None: interp = Interp(locals=locals_) interp.write = self.send_to_stdouterr # type: ignore - if banner is None: - if config.help_key: - banner = ( - _("Welcome to bpython!") - + " " - + _("Press <%s> for help.") % config.help_key - ) - else: - banner = None if config.cli_suggestion_width <= 0 or config.cli_suggestion_width > 1: config.cli_suggestion_width = 1 @@ -493,15 +484,15 @@ def __init__( # The methods below should be overridden, but the default implementations # below can be used as well. - def get_cursor_vertical_diff(self): + def get_cursor_vertical_diff(self) -> int: """Return how the cursor moved due to a window size change""" return 0 - def get_top_usable_line(self): + def get_top_usable_line(self) -> int: """Return the top line of display that can be rewritten""" return 0 - def get_term_hw(self): + def get_term_hw(self) -> tuple[int, int]: """Returns the current width and height of the display area.""" return (50, 10) From 410407c3be7bcaaddab9ae5baa507bbc91047a79 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 17:21:13 +0200 Subject: [PATCH 1626/1650] Pass correctly typed values --- bpython/test/test_config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bpython/test/test_config.py b/bpython/test/test_config.py index 2d2e5e820..c34f2dac6 100644 --- a/bpython/test/test_config.py +++ b/bpython/test/test_config.py @@ -2,10 +2,11 @@ import tempfile import textwrap import unittest +from pathlib import Path from bpython import config -TEST_THEME_PATH = os.path.join(os.path.dirname(__file__), "test.theme") +TEST_THEME_PATH = Path(os.path.join(os.path.dirname(__file__), "test.theme")) class TestConfig(unittest.TestCase): @@ -16,7 +17,7 @@ def load_temp_config(self, content): f.write(content.encode("utf8")) f.flush() - return config.Config(f.name) + return config.Config(Path(f.name)) def test_load_theme(self): color_scheme = dict() From a283365bfdf2c51fe23473714b6bb5c5caf40fa6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 17:22:09 +0200 Subject: [PATCH 1627/1650] Handle unspecified config paths (fixes #1027) --- bpython/config.py | 9 ++++++--- bpython/repl.py | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bpython/config.py b/bpython/config.py index 27af87402..c309403fd 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -207,13 +207,14 @@ class Config: }, } - def __init__(self, config_path: Path) -> None: + def __init__(self, config_path: Path | None = None) -> None: """Loads .ini configuration file and stores its values.""" config = ConfigParser() fill_config_with_default_values(config, self.defaults) try: - config.read(config_path) + if config_path is not None: + config.read(config_path) except UnicodeDecodeError as e: sys.stderr.write( "Error: Unable to parse config file at '{}' due to an " @@ -243,7 +244,9 @@ def get_key_no_doublebind(command: str) -> str: return requested_key - self.config_path = Path(config_path).absolute() + self.config_path = ( + config_path.absolute() if config_path is not None else None + ) self.hist_file = Path(config.get("general", "hist_file")).expanduser() self.dedent_after = config.getint("general", "dedent_after") diff --git a/bpython/repl.py b/bpython/repl.py index f07494b6e..8d4c305a7 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -1216,6 +1216,10 @@ def open_in_external_editor(self, filename): return subprocess.call(args) == 0 def edit_config(self): + if self.config.config_path is None: + self.interact.notify(_("No config file specified.")) + return + if not self.config.config_path.is_file(): if self.interact.confirm( _("Config file does not exist - create new from default? (y/N)") From c4d5dec67e93697dbaaf17a5a4cc728cc671411f Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 17:38:59 +0200 Subject: [PATCH 1628/1650] Remove welcome message from tests --- bpython/test/test_curtsies_painting.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 19561efb9..061facf2c 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -98,7 +98,7 @@ def test_history_is_cleared(self): class TestCurtsiesPaintingSimple(CurtsiesPaintingTest): def test_startup(self): - screen = fsarray([cyan(">>> "), cyan("Welcome to")]) + screen = fsarray([cyan(">>> ")]) self.assert_paint(screen, (0, 4)) def test_enter_text(self): @@ -112,8 +112,7 @@ def test_enter_text(self): + yellow("+") + cyan(" ") + green("1") - ), - cyan("Welcome to"), + ) ] ) self.assert_paint(screen, (0, 9)) @@ -124,7 +123,7 @@ def test_run_line(self): sys.stdout = self.repl.stdout [self.repl.add_normal_character(c) for c in "1 + 1"] self.repl.on_enter(new_code=False) - screen = fsarray([">>> 1 + 1", "2", "Welcome to"]) + screen = fsarray([">>> 1 + 1", "2"]) self.assert_paint_ignoring_formatting(screen, (1, 1)) finally: sys.stdout = orig_stdout @@ -135,19 +134,10 @@ def test_completion(self): self.cursor_offset = 2 screen = self.process_box_characters( [ - ">>> an", - "┌──────────────────────────────┐", - "│ and any( │", - "└──────────────────────────────┘", - "Welcome to bpython! Press f", - ] - if sys.version_info[:2] < (3, 10) - else [ ">>> an", "┌──────────────────────────────┐", "│ and anext( any( │", "└──────────────────────────────┘", - "Welcome to bpython! Press f", ] ) self.assert_paint_ignoring_formatting(screen, (0, 4)) From 290efc6d8fde2a0a481c207675671b6ffedc23f5 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 17:49:37 +0200 Subject: [PATCH 1629/1650] Specify test width --- bpython/test/test_curtsies_painting.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 061facf2c..1ce455e53 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -98,7 +98,7 @@ def test_history_is_cleared(self): class TestCurtsiesPaintingSimple(CurtsiesPaintingTest): def test_startup(self): - screen = fsarray([cyan(">>> ")]) + screen = fsarray([cyan(">>> ")], width=10) self.assert_paint(screen, (0, 4)) def test_enter_text(self): @@ -112,8 +112,9 @@ def test_enter_text(self): + yellow("+") + cyan(" ") + green("1") - ) - ] + ), + ], + width=10, ) self.assert_paint(screen, (0, 9)) From 68bee30af321a36e3ad0393fbc590e0bc27b0995 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 17:56:05 +0200 Subject: [PATCH 1630/1650] Fix override --- bpython/curtsiesfrontend/repl.py | 3 ++- bpython/repl.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 5dd8d988b..76ee34473 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -1250,7 +1250,7 @@ def predicted_indent(self, line): logger.debug("indent we found was %s", indent) return indent - def push(self, line, insert_into_history=True): + def push(self, line, insert_into_history=True) -> bool: """Push a line of code onto the buffer, start running the buffer If the interpreter successfully runs the code, clear the buffer @@ -1297,6 +1297,7 @@ def push(self, line, insert_into_history=True): self.coderunner.load_code(code_to_run) self.run_code_and_maybe_finish() + return not code_will_parse def run_code_and_maybe_finish(self, for_code=None): r = self.coderunner.run_code(for_code=for_code) diff --git a/bpython/repl.py b/bpython/repl.py index 8d4c305a7..2ced5b7a8 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -989,11 +989,11 @@ def do_pastebin(self, s) -> str | None: return paste_url - def push(self, s, insert_into_history=True) -> bool: + def push(self, line, insert_into_history=True) -> bool: """Push a line of code onto the buffer so it can process it all at once when a code block ends""" # This push method is used by cli and urwid, but not curtsies - s = s.rstrip("\n") + s = line.rstrip("\n") self.buffer.append(s) if insert_into_history: From 55fa6017b0d2c0a6390c11e6140e9e2021e671c2 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 17:56:16 +0200 Subject: [PATCH 1631/1650] Fix potentially unbound variable --- bpython/test/test_curtsies_painting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 1ce455e53..fdb9dcad4 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -119,8 +119,8 @@ def test_enter_text(self): self.assert_paint(screen, (0, 9)) def test_run_line(self): + orig_stdout = sys.stdout try: - orig_stdout = sys.stdout sys.stdout = self.repl.stdout [self.repl.add_normal_character(c) for c in "1 + 1"] self.repl.on_enter(new_code=False) From a5306af72cfa44d5e33ee7291a1ad57025b8eefe Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 19:34:33 +0200 Subject: [PATCH 1632/1650] Bump copyright years --- bpython/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/__init__.py b/bpython/__init__.py index 26fa3e63d..7d7bd28e0 100644 --- a/bpython/__init__.py +++ b/bpython/__init__.py @@ -31,7 +31,7 @@ __author__ = ( "Bob Farrell, Andreas Stuehrk, Sebastian Ramacher, Thomas Ballinger, et al." ) -__copyright__ = f"(C) 2008-2024 {__author__}" +__copyright__ = f"(C) 2008-2025 {__author__}" __license__ = "MIT" __version__ = version package_dir = os.path.abspath(os.path.dirname(__file__)) From 9b995435b2a06373aadbdb744c20fe75a86aa0f9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 19:35:17 +0200 Subject: [PATCH 1633/1650] Remove unused imports --- bpython/args.py | 1 - bpython/autocomplete.py | 4 ---- bpython/curtsiesfrontend/_internal.py | 2 +- bpython/curtsiesfrontend/filewatch.py | 1 - bpython/curtsiesfrontend/interpreter.py | 2 +- bpython/curtsiesfrontend/parse.py | 2 +- bpython/curtsiesfrontend/repl.py | 6 ------ bpython/filelock.py | 2 +- bpython/history.py | 2 +- bpython/importcompletion.py | 1 - bpython/inspection.py | 4 ---- bpython/keys.py | 2 +- bpython/lazyre.py | 1 - bpython/line.py | 1 - bpython/pager.py | 1 - bpython/paste.py | 2 +- bpython/patch_linecache.py | 2 +- bpython/urwid.py | 1 - 18 files changed, 8 insertions(+), 29 deletions(-) diff --git a/bpython/args.py b/bpython/args.py index 35fd3e7bf..cee4bcbfa 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -36,7 +36,6 @@ import os import sys from pathlib import Path -from typing import Tuple, List, Optional from collections.abc import Callable from types import ModuleType diff --git a/bpython/autocomplete.py b/bpython/autocomplete.py index 4fb62f720..77887ef4b 100644 --- a/bpython/autocomplete.py +++ b/bpython/autocomplete.py @@ -39,11 +39,7 @@ from enum import Enum from typing import ( Any, - Dict, - List, Optional, - Set, - Tuple, ) from collections.abc import Iterator, Sequence diff --git a/bpython/curtsiesfrontend/_internal.py b/bpython/curtsiesfrontend/_internal.py index 8c070b34c..72572b0b1 100644 --- a/bpython/curtsiesfrontend/_internal.py +++ b/bpython/curtsiesfrontend/_internal.py @@ -22,7 +22,7 @@ import pydoc from types import TracebackType -from typing import Optional, Type, Literal +from typing import Literal from .. import _internal diff --git a/bpython/curtsiesfrontend/filewatch.py b/bpython/curtsiesfrontend/filewatch.py index 2822db6df..b9778c97a 100644 --- a/bpython/curtsiesfrontend/filewatch.py +++ b/bpython/curtsiesfrontend/filewatch.py @@ -1,6 +1,5 @@ import os from collections import defaultdict -from typing import Dict, Set, List from collections.abc import Callable, Iterable, Sequence from .. import importcompletion diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index 280c56ede..9382db6bc 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,6 +1,6 @@ import sys from codeop import CommandCompiler -from typing import Any, Dict, Optional, Tuple, Union +from typing import Any from collections.abc import Iterable from pygments.token import Generic, Token, Keyword, Name, Comment, String diff --git a/bpython/curtsiesfrontend/parse.py b/bpython/curtsiesfrontend/parse.py index 28b32e649..122f1ee9f 100644 --- a/bpython/curtsiesfrontend/parse.py +++ b/bpython/curtsiesfrontend/parse.py @@ -1,6 +1,6 @@ import re from functools import partial -from typing import Any, Dict, Tuple +from typing import Any from collections.abc import Callable from curtsies.formatstring import fmtstr, FmtStr diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 76ee34473..928be253e 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -14,13 +14,7 @@ from types import FrameType, TracebackType from typing import ( Any, - Dict, - List, Literal, - Optional, - Tuple, - Type, - Union, ) from collections.abc import Iterable, Sequence diff --git a/bpython/filelock.py b/bpython/filelock.py index b8eb11ff2..add2eb818 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -from typing import Optional, Type, IO, Literal +from typing import IO, Literal from types import TracebackType has_fcntl = True diff --git a/bpython/history.py b/bpython/history.py index b58309b53..27852e837 100644 --- a/bpython/history.py +++ b/bpython/history.py @@ -25,7 +25,7 @@ from pathlib import Path import stat from itertools import islice, chain -from typing import Optional, List, TextIO +from typing import TextIO from collections.abc import Iterable from .translations import _ diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 570996d44..00860c160 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -27,7 +27,6 @@ import warnings from dataclasses import dataclass from pathlib import Path -from typing import Optional, Set, Union from collections.abc import Generator, Sequence, Iterable from .line import ( diff --git a/bpython/inspection.py b/bpython/inspection.py index 63f0e2d38..d3e2d5e56 100644 --- a/bpython/inspection.py +++ b/bpython/inspection.py @@ -28,10 +28,6 @@ from dataclasses import dataclass from typing import ( Any, - Optional, - Type, - Dict, - List, ContextManager, Literal, ) diff --git a/bpython/keys.py b/bpython/keys.py index 1068a4f26..51f4c0117 100644 --- a/bpython/keys.py +++ b/bpython/keys.py @@ -21,7 +21,7 @@ # THE SOFTWARE. import string -from typing import TypeVar, Generic, Tuple, Dict +from typing import TypeVar, Generic T = TypeVar("T") diff --git a/bpython/lazyre.py b/bpython/lazyre.py index 1d9036164..3d1bd372f 100644 --- a/bpython/lazyre.py +++ b/bpython/lazyre.py @@ -24,7 +24,6 @@ from collections.abc import Iterator from functools import cached_property from re import Pattern, Match -from typing import Optional, Optional class LazyReCompile: diff --git a/bpython/line.py b/bpython/line.py index e64b20d90..83a75f09e 100644 --- a/bpython/line.py +++ b/bpython/line.py @@ -8,7 +8,6 @@ from dataclasses import dataclass from itertools import chain -from typing import Optional, Tuple from .lazyre import LazyReCompile diff --git a/bpython/pager.py b/bpython/pager.py index 2fa4846e0..deb144b93 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -30,7 +30,6 @@ import subprocess import sys import shlex -from typing import List def get_pager_command(default: str = "less -rf") -> list[str]: diff --git a/bpython/paste.py b/bpython/paste.py index 8ca6f2df5..e43ce2f22 100644 --- a/bpython/paste.py +++ b/bpython/paste.py @@ -22,7 +22,7 @@ import errno import subprocess -from typing import Optional, Tuple, Protocol +from typing import Protocol from urllib.parse import urljoin, urlparse import requests diff --git a/bpython/patch_linecache.py b/bpython/patch_linecache.py index 68787e709..fa8e17294 100644 --- a/bpython/patch_linecache.py +++ b/bpython/patch_linecache.py @@ -1,5 +1,5 @@ import linecache -from typing import Any, List, Tuple, Optional +from typing import Any class BPythonLinecache(dict): diff --git a/bpython/urwid.py b/bpython/urwid.py index d94fc2e77..40abb421a 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -38,7 +38,6 @@ import locale import signal import urwid -from typing import Optional from . import args as bpargs, repl, translations from .formatter import theme_map From 4709825cb96129d932207d437399630e5ece7796 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 20:54:37 +0200 Subject: [PATCH 1634/1650] Remove unused function --- bpython/curtsiesfrontend/manual_readline.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/bpython/curtsiesfrontend/manual_readline.py b/bpython/curtsiesfrontend/manual_readline.py index 206e5278b..3d02c024a 100644 --- a/bpython/curtsiesfrontend/manual_readline.py +++ b/bpython/curtsiesfrontend/manual_readline.py @@ -4,9 +4,9 @@ and the cursor location based on http://www.bigsmoke.us/readline/shortcuts""" -from ..lazyre import LazyReCompile import inspect +from ..lazyre import LazyReCompile from ..line import cursor_on_closing_char_pair INDENT = 4 @@ -68,12 +68,6 @@ def call(self, key, **kwargs): args = {k: v for k, v in kwargs.items() if k in params} return func(**args) - def call_without_cut(self, key, **kwargs): - """Looks up the function and calls it, returning only line and cursor - offset""" - r = self.call_for_two(key, **kwargs) - return r[:2] - def __contains__(self, key): return key in self.simple_edits or key in self.cut_buffer_edits From a04d423f253630bef8ad9d25b4c2c5be7dc354ac Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 21:27:17 +0200 Subject: [PATCH 1635/1650] Refactor --- bpython/filelock.py | 108 ++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/bpython/filelock.py b/bpython/filelock.py index add2eb818..c106c4155 100644 --- a/bpython/filelock.py +++ b/bpython/filelock.py @@ -23,20 +23,6 @@ from typing import IO, Literal from types import TracebackType -has_fcntl = True -try: - import fcntl - import errno -except ImportError: - has_fcntl = False - -has_msvcrt = True -try: - import msvcrt - import os -except ImportError: - has_msvcrt = False - class BaseLock: """Base class for file locking""" @@ -69,56 +55,72 @@ def __del__(self) -> None: self.release() -class UnixFileLock(BaseLock): - """Simple file locking for Unix using fcntl""" +try: + import fcntl + import errno - def __init__(self, fileobj, mode: int = 0) -> None: - super().__init__() - self.fileobj = fileobj - self.mode = mode | fcntl.LOCK_EX + class UnixFileLock(BaseLock): + """Simple file locking for Unix using fcntl""" - def acquire(self) -> None: - try: - fcntl.flock(self.fileobj, self.mode) - self.locked = True - except OSError as e: - if e.errno != errno.ENOLCK: - raise e + def __init__(self, fileobj, mode: int = 0) -> None: + super().__init__() + self.fileobj = fileobj + self.mode = mode | fcntl.LOCK_EX - def release(self) -> None: - self.locked = False - fcntl.flock(self.fileobj, fcntl.LOCK_UN) + def acquire(self) -> None: + try: + fcntl.flock(self.fileobj, self.mode) + self.locked = True + except OSError as e: + if e.errno != errno.ENOLCK: + raise e + def release(self) -> None: + self.locked = False + fcntl.flock(self.fileobj, fcntl.LOCK_UN) -class WindowsFileLock(BaseLock): - """Simple file locking for Windows using msvcrt""" + has_fcntl = True +except ImportError: + has_fcntl = False - def __init__(self, filename: str) -> None: - super().__init__() - self.filename = f"{filename}.lock" - self.fileobj = -1 - def acquire(self) -> None: - # create a lock file and lock it - self.fileobj = os.open( - self.filename, os.O_RDWR | os.O_CREAT | os.O_TRUNC - ) - msvcrt.locking(self.fileobj, msvcrt.LK_NBLCK, 1) +try: + import msvcrt + import os - self.locked = True + class WindowsFileLock(BaseLock): + """Simple file locking for Windows using msvcrt""" - def release(self) -> None: - self.locked = False + def __init__(self, filename: str) -> None: + super().__init__() + self.filename = f"{filename}.lock" + self.fileobj = -1 + + def acquire(self) -> None: + # create a lock file and lock it + self.fileobj = os.open( + self.filename, os.O_RDWR | os.O_CREAT | os.O_TRUNC + ) + msvcrt.locking(self.fileobj, msvcrt.LK_NBLCK, 1) + + self.locked = True - # unlock lock file and remove it - msvcrt.locking(self.fileobj, msvcrt.LK_UNLCK, 1) - os.close(self.fileobj) - self.fileobj = -1 + def release(self) -> None: + self.locked = False - try: - os.remove(self.filename) - except OSError: - pass + # unlock lock file and remove it + msvcrt.locking(self.fileobj, msvcrt.LK_UNLCK, 1) + os.close(self.fileobj) + self.fileobj = -1 + + try: + os.remove(self.filename) + except OSError: + pass + + has_msvcrt = True +except ImportError: + has_msvcrt = False def FileLock( From 82416c3c023f5f2cb5ec3f3833f8c785d41022cd Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Fri, 26 Sep 2025 21:27:23 +0200 Subject: [PATCH 1636/1650] Refactor --- bpython/pager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bpython/pager.py b/bpython/pager.py index deb144b93..af9370d6c 100644 --- a/bpython/pager.py +++ b/bpython/pager.py @@ -33,8 +33,7 @@ def get_pager_command(default: str = "less -rf") -> list[str]: - command = shlex.split(os.environ.get("PAGER", default)) - return command + return shlex.split(os.environ.get("PAGER", default)) def page_internal(data: str) -> None: From aed3ebcd81866f4a187cf3654201ecc153ebe1ec Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Mon, 27 Oct 2025 20:55:48 +0100 Subject: [PATCH 1637/1650] Update changelog for 0.26 release --- CHANGELOG.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f55fe76fd..a4aa42d2b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,16 +6,21 @@ Changelog General information: +* This release is focused on Python 3.14 support. New features: Fixes: - +* #1027: Handle unspecified config paths +* #1035: Align simple_eval with Python 3.10+ +* #1036: Make -q hide the welcome message +* #1041: Convert sys.ps1 to a string to work-around non-str sys.ps1 from vscode Changes to dependencies: +Support for Python 3.14 has been added. Support for Python 3.9 has been dropped. 0.25 ---- From ec6603d790d81ac23c4fd54e2fa26f744651a2bb Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 08:30:37 +0100 Subject: [PATCH 1638/1650] Start development of 0.27 --- CHANGELOG.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a4aa42d2b..34dd4fb54 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,21 @@ Changelog ========= +0.27 +---- + +General information: + + +New features: + + +Fixes: + + +Changes to dependencies: + + 0.26 ---- From c1f4c331dd35c4ffec2f92cea24b1a6860240382 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 08:31:20 +0100 Subject: [PATCH 1639/1650] Update meta data to require >= 3.10 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 7d61ee1ce..dee3e2743 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,7 @@ classifiers = Programming Language :: Python :: 3 [options] -python_requires = >=3.9 +python_requires = >=3.10 packages = bpython bpython.curtsiesfrontend From 3d6389bd877331478de913a5ae8eb8bd7cef1f01 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 21:58:13 +0100 Subject: [PATCH 1640/1650] Drop support for Python 3.10 --- .github/workflows/build.yaml | 3 +-- bpython/_typing_compat.py | 27 --------------------------- bpython/args.py | 6 +++--- pyproject.toml | 2 +- setup.cfg | 2 +- 5 files changed, 6 insertions(+), 34 deletions(-) delete mode 100644 bpython/_typing_compat.py diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4372d832c..b5e057597 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -15,11 +15,10 @@ jobs: fail-fast: false matrix: python-version: - - "3.10" - "3.11" - "3.12" - "3.13" - - "pypy-3.10" + - "pypy-3.11" steps: - uses: actions/checkout@v5 with: diff --git a/bpython/_typing_compat.py b/bpython/_typing_compat.py deleted file mode 100644 index 5d9a36079..000000000 --- a/bpython/_typing_compat.py +++ /dev/null @@ -1,27 +0,0 @@ -# The MIT License -# -# Copyright (c) 2024 Sebastian Ramacher -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -try: - # introduced in Python 3.11 - from typing import Never -except ImportError: - from typing_extensions import Never # type: ignore diff --git a/bpython/args.py b/bpython/args.py index cee4bcbfa..ac78267a9 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -1,7 +1,7 @@ # The MIT License # # Copyright (c) 2008 Bob Farrell -# Copyright (c) 2012-2021 Sebastian Ramacher +# Copyright (c) 2012-2025 Sebastian Ramacher # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -38,11 +38,11 @@ from pathlib import Path from collections.abc import Callable from types import ModuleType +from typing import Never from . import __version__, __copyright__ from .config import default_config_path, Config from .translations import _ -from ._typing_compat import Never logger = logging.getLogger(__name__) @@ -52,7 +52,7 @@ class ArgumentParserFailed(ValueError): class RaisingArgumentParser(argparse.ArgumentParser): - def error(self, msg: str) -> Never: + def error(self, message: str) -> Never: raise ArgumentParserFailed() diff --git a/pyproject.toml b/pyproject.toml index 0a891d27c..40efff3e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 80 -target_version = ["py310"] +target_version = ["py311"] include = '\.pyi?$' exclude = ''' /( diff --git a/setup.cfg b/setup.cfg index dee3e2743..07fb34e50 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,7 @@ classifiers = Programming Language :: Python :: 3 [options] -python_requires = >=3.10 +python_requires = >=3.11 packages = bpython bpython.curtsiesfrontend From d5a8af480b2fb8aa5cecc1e932316d1b666dbae8 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 21:58:22 +0100 Subject: [PATCH 1641/1650] CI: test 3.14 --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b5e057597..0d297e51b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -18,6 +18,7 @@ jobs: - "3.11" - "3.12" - "3.13" + - "3.14" - "pypy-3.11" steps: - uses: actions/checkout@v5 From 20241ea355fe099e977beff40af7f2313b80c541 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 22:03:31 +0100 Subject: [PATCH 1642/1650] Remove workaround for < Python 3.11 --- bpython/test/test_interpreter.py | 87 +++++--------------------------- 1 file changed, 12 insertions(+), 75 deletions(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index b9f0a31e2..7245ce835 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -21,66 +21,17 @@ def test_syntaxerror(self): i.runsource("1.1.1.1") - if (3, 10, 1) <= sys.version_info[:3]: - expected = ( - " File " - + green('""') - + ", line " - + bold(magenta("1")) - + "\n 1.1.1.1\n ^^\n" - + bold(red("SyntaxError")) - + ": " - + cyan("invalid syntax") - + "\n" - ) - elif (3, 10) <= sys.version_info[:2]: - expected = ( - " File " - + green('""') - + ", line " - + bold(magenta("1")) - + "\n 1.1.1.1\n ^^^^^\n" - + bold(red("SyntaxError")) - + ": " - + cyan("invalid syntax. Perhaps you forgot a comma?") - + "\n" - ) - elif (3, 8) <= sys.version_info[:2]: - expected = ( - " File " - + green('""') - + ", line " - + bold(magenta("1")) - + "\n 1.1.1.1\n ^\n" - + bold(red("SyntaxError")) - + ": " - + cyan("invalid syntax") - + "\n" - ) - elif pypy: - expected = ( - " File " - + green('""') - + ", line " - + bold(magenta("1")) - + "\n 1.1.1.1\n ^\n" - + bold(red("SyntaxError")) - + ": " - + cyan("invalid syntax") - + "\n" - ) - else: - expected = ( - " File " - + green('""') - + ", line " - + bold(magenta("1")) - + "\n 1.1.1.1\n ^\n" - + bold(red("SyntaxError")) - + ": " - + cyan("invalid syntax") - + "\n" - ) + expected = ( + " File " + + green('""') + + ", line " + + bold(magenta("1")) + + "\n 1.1.1.1\n ^^\n" + + bold(red("SyntaxError")) + + ": " + + cyan("invalid syntax") + + "\n" + ) a = i.a self.assertMultiLineEqual(str(plain("").join(a)), str(expected)) @@ -114,7 +65,7 @@ def gfunc(): + cyan(global_not_found) + "\n" ) - elif (3, 11) <= sys.version_info[:2]: + else: expected = ( "Traceback (most recent call last):\n File " + green('""') @@ -129,20 +80,6 @@ def gfunc(): + cyan(global_not_found) + "\n" ) - else: - expected = ( - "Traceback (most recent call last):\n File " - + green('""') - + ", line " - + bold(magenta("1")) - + ", in " - + cyan("") - + "\n gfunc()\n" - + bold(red("NameError")) - + ": " - + cyan(global_not_found) - + "\n" - ) a = i.a self.assertMultiLineEqual(str(expected), str(plain("").join(a))) From b6494ffba5f0abdb4596bb2ba059a2c811438ded Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 22:07:08 +0100 Subject: [PATCH 1643/1650] Fix tests with pypy --- bpython/test/test_interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/test/test_interpreter.py b/bpython/test/test_interpreter.py index 7245ce835..e5bc08956 100644 --- a/bpython/test/test_interpreter.py +++ b/bpython/test/test_interpreter.py @@ -50,7 +50,7 @@ def gfunc(): global_not_found = "name 'gfunc' is not defined" - if (3, 13) <= sys.version_info[:2]: + if (3, 13) <= sys.version_info[:2] or pypy: expected = ( "Traceback (most recent call last):\n File " + green('""') From 11d63cc04b34753e05c7131bdcd0c8c89e3d5d1c Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 23:01:23 +0100 Subject: [PATCH 1644/1650] Require urwid >= 1.0 --- bpython/urwid.py | 34 +--------------------------------- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index 40abb421a..882641e8d 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -95,39 +95,7 @@ def buildProtocol(self, addr): # If Twisted is not available urwid has no TwistedEventLoop attribute. # Code below will try to import reactor before using TwistedEventLoop. # I assume TwistedEventLoop will be available if that import succeeds. -if urwid.VERSION < (1, 0, 0) and hasattr(urwid, "TwistedEventLoop"): - - class TwistedEventLoop(urwid.TwistedEventLoop): - """TwistedEventLoop modified to properly stop the reactor. - - urwid 0.9.9 and 0.9.9.1 crash the reactor on ExitMainLoop instead - of stopping it. One obvious way this breaks is if anything used - the reactor's thread pool: that thread pool is not shut down if - the reactor is not stopped, which means python hangs on exit - (joining the non-daemon threadpool threads that never exit). And - the default resolver is the ThreadedResolver, so if we looked up - any names we hang on exit. That is bad enough that we hack up - urwid a bit here to exit properly. - """ - - def handle_exit(self, f): - def wrapper(*args, **kwargs): - try: - return f(*args, **kwargs) - except urwid.ExitMainLoop: - # This is our change. - self.reactor.stop() - except: - # This is the same as in urwid. - # We are obviously not supposed to ever hit this. - print(sys.exc_info()) - self._exc_info = sys.exc_info() - self.reactor.crash() - - return wrapper - -else: - TwistedEventLoop = getattr(urwid, "TwistedEventLoop", None) +TwistedEventLoop = getattr(urwid, "TwistedEventLoop", None) class StatusbarEdit(urwid.Edit): diff --git a/setup.cfg b/setup.cfg index 07fb34e50..f14998b77 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,7 +35,7 @@ install_requires = [options.extras_require] clipboard = pyperclip jedi = jedi >= 0.16 -urwid = urwid < 3.0 +urwid = urwid >=1.0,< 3.0 watch = watchdog [options.entry_points] From bbc9438a2638d60f131ede56d7c21a6f96592927 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 23:12:28 +0100 Subject: [PATCH 1645/1650] Fix compatibility with urwid 3.0 (fixes #1043) --- .github/workflows/build.yaml | 2 +- bpython/urwid.py | 8 ++++++-- setup.cfg | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0d297e51b..1b6fabf3e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -32,7 +32,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install "urwid < 3.0" twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" + pip install "urwid >= 1.0" twisted watchdog "jedi >=0.16" babel "sphinx >=1.5" pip install pytest pytest-cov numpy - name: Build with Python ${{ matrix.python-version }} run: | diff --git a/bpython/urwid.py b/bpython/urwid.py index 882641e8d..8be003705 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -411,7 +411,7 @@ def keypress(self, size, key): return key -class Tooltip(urwid.BoxWidget): +class Tooltip(urwid.Widget): """Container inspired by Overlay to position our tooltip. bottom_w should be a BoxWidget. @@ -423,6 +423,9 @@ class Tooltip(urwid.BoxWidget): from the bottom window and hides it if there is no cursor. """ + _sizing = frozenset(['box']) + _selectable = True + def __init__(self, bottom_w, listbox): super().__init__() @@ -1322,7 +1325,8 @@ def run_find_coroutine(): run_find_coroutine() - myrepl.main_loop.screen.run_wrapper(run_with_screen_before_mainloop) + with myrepl.main_loop.screen.start(): + run_with_screen_before_mainloop() if config.flush_output and not options.quiet: sys.stdout.write(myrepl.getstdout()) diff --git a/setup.cfg b/setup.cfg index f14998b77..e17199211 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,7 +35,7 @@ install_requires = [options.extras_require] clipboard = pyperclip jedi = jedi >= 0.16 -urwid = urwid >=1.0,< 3.0 +urwid = urwid >=1.0 watch = watchdog [options.entry_points] From 105380ad95063a91e153dde41a198accc38917e6 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 23:15:26 +0100 Subject: [PATCH 1646/1650] Apply black --- bpython/urwid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index 8be003705..123417d47 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -423,7 +423,7 @@ class Tooltip(urwid.Widget): from the bottom window and hides it if there is no cursor. """ - _sizing = frozenset(['box']) + _sizing = frozenset(["box"]) _selectable = True def __init__(self, bottom_w, listbox): From 445aff06400cad84e2482c63199dfda0a848dd59 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 23:20:26 +0100 Subject: [PATCH 1647/1650] Remove unnecessary version check --- bpython/importcompletion.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bpython/importcompletion.py b/bpython/importcompletion.py index 00860c160..e22b61f62 100644 --- a/bpython/importcompletion.py +++ b/bpython/importcompletion.py @@ -48,12 +48,8 @@ ), ) -_LOADED_INODE_DATACLASS_ARGS = {"frozen": True} -if sys.version_info[:2] >= (3, 10): - _LOADED_INODE_DATACLASS_ARGS["slots"] = True - -@dataclass(**_LOADED_INODE_DATACLASS_ARGS) +@dataclass(frozen=True, slots=True) class _LoadedInode: dev: int inode: int From e33828ea2c908a6716610a00f25bc2eff025adbe Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 23:25:01 +0100 Subject: [PATCH 1648/1650] Remove unnecessary version checks --- bpython/test/test_inspection.py | 10 +--------- bpython/test/test_simpleeval.py | 3 --- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/bpython/test/test_inspection.py b/bpython/test/test_inspection.py index c83ca0128..30e911021 100644 --- a/bpython/test/test_inspection.py +++ b/bpython/test/test_inspection.py @@ -11,7 +11,6 @@ from bpython.test.fodder import encoding_utf8 pypy = "PyPy" in sys.version -_is_py311 = sys.version_info[:2] >= (3, 11) try: import numpy @@ -127,14 +126,7 @@ def test_getfuncprops_print(self): self.assertIn("file", props.argspec.kwonly) self.assertIn("flush", props.argspec.kwonly) self.assertIn("sep", props.argspec.kwonly) - if _is_py311: - self.assertEqual( - repr(props.argspec.kwonly_defaults["file"]), "None" - ) - else: - self.assertEqual( - repr(props.argspec.kwonly_defaults["file"]), "sys.stdout" - ) + self.assertEqual(repr(props.argspec.kwonly_defaults["file"]), "None") self.assertEqual(repr(props.argspec.kwonly_defaults["flush"]), "False") @unittest.skipUnless( diff --git a/bpython/test/test_simpleeval.py b/bpython/test/test_simpleeval.py index 1d1a3f1a3..8bdb19296 100644 --- a/bpython/test/test_simpleeval.py +++ b/bpython/test/test_simpleeval.py @@ -20,9 +20,6 @@ def test_matches_stdlib(self): self.assertMatchesStdlib("{(1,): [2,3,{}]}") self.assertMatchesStdlib("{1, 2}") - @unittest.skipUnless( - sys.version_info[:2] >= (3, 9), "Only Python3.9 evaluates set()" - ) def test_matches_stdlib_set_literal(self): """set() is evaluated""" self.assertMatchesStdlib("set()") From 9004accdf1cf971caccc58e9d4acdd997687b80b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 08:19:39 +0000 Subject: [PATCH 1649/1650] Bump actions/checkout from 5 to 6 (#1048) --- .github/workflows/build.yaml | 2 +- .github/workflows/lint.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1b6fabf3e..8e04fae2c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,7 +21,7 @@ jobs: - "3.14" - "pypy-3.11" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 45d4c5f61..8caf95623 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -8,7 +8,7 @@ jobs: black: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 - name: Install dependencies @@ -21,7 +21,7 @@ jobs: codespell: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: codespell-project/actions-codespell@master with: skip: "*.po,encoding_latin1.py,test_repl.py" @@ -30,7 +30,7 @@ jobs: mypy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 - name: Install dependencies From 89e2f64050cda18f95d56141c3b9cac3a5a609f9 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Tue, 28 Oct 2025 23:37:02 +0100 Subject: [PATCH 1650/1650] Refactor --- bpython/urwid.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/bpython/urwid.py b/bpython/urwid.py index 123417d47..d4899332d 100644 --- a/bpython/urwid.py +++ b/bpython/urwid.py @@ -225,17 +225,11 @@ def _on_prompt_enter(self, edit, new_text): urwid.register_signal(Statusbar, "prompt_result") -def decoding_input_filter(keys, raw): +def decoding_input_filter(keys: list[str], _raw: list[int]) -> list[str]: """Input filter for urwid which decodes each key with the locale's preferred encoding.'""" encoding = locale.getpreferredencoding() - converted_keys = list() - for key in keys: - if isinstance(key, str): - converted_keys.append(key.decode(encoding)) - else: - converted_keys.append(key) - return converted_keys + return [key.decode(encoding) for key in keys] def format_tokens(tokensource):