From ba4b5e811a9adc994d1099a1e2a36e23cd3d48f7 2010-10-05 17:38:23 From: Fernando Perez Date: 2010-10-05 17:38:23 Subject: [PATCH] Created HistoryManager to better organize history control. This will put in a single location all history-related operations, and allow us to simplify the main classes further. Readline dependencies remain (for history saving/loading), this is only a first refactoring step. --- diff --git a/IPython/core/history.py b/IPython/core/history.py index 8be2c49..b92e16b 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -1,14 +1,163 @@ -# -*- coding: utf-8 -*- """ History related magics and functionality """ +#----------------------------------------------------------------------------- +# Copyright (C) 2010 The IPython Development Team. +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +from __future__ import print_function # Stdlib imports import fnmatch import os +import sys +# Our own packages import IPython.utils.io + +from IPython.core import ipapi +from IPython.core.inputlist import InputList +from IPython.utils.pickleshare import PickleShareDB from IPython.utils.io import ask_yes_no from IPython.utils.warn import warn -from IPython.core import ipapi + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +class HistoryManager(object): + """A class to organize all history-related functionality in one place. + """ + def __init__(self, shell): + """Create a new history manager associated with a shell instance. + """ + self.shell = shell + + # List of input with multi-line handling. + self.input_hist = InputList() + # This one will hold the 'raw' input history, without any + # pre-processing. This will allow users to retrieve the input just as + # it was exactly typed in by the user, with %hist -r. + self.input_hist_raw = InputList() + + # list of visited directories + try: + self.dir_hist = [os.getcwd()] + except OSError: + self.dir_hist = [] + + # dict of output history + self.output_hist = {} + + # Now the history file + if shell.profile: + histfname = 'history-%s' % shell.profile + else: + histfname = 'history' + self.hist_file = os.path.join(shell.ipython_dir, histfname) + + # Fill the history zero entry, user counter starts at 1 + self.input_hist.append('\n') + self.input_hist_raw.append('\n') + + # Objects related to shadow history management + self._init_shadow_hist() + + # For backwards compatibility, we must put these back in the shell + # object, until we've removed all direct uses of the history objects in + # the shell itself. + shell.input_hist = self.input_hist + shell.input_hist_raw = self.input_hist_raw + shell.output_hist = self.output_hist + shell.dir_hist = self.dir_hist + shell.histfile = self.hist_file + shell.shadowhist = self.shadow_hist + shell.db = self.shadow_db + + def _init_shadow_hist(self): + try: + self.shadow_db = PickleShareDB(os.path.join( + self.shell.ipython_dir, 'db')) + except UnicodeDecodeError: + print("Your ipython_dir can't be decoded to unicode!") + print("Please set HOME environment variable to something that") + print(r"only has ASCII characters, e.g. c:\home") + print("Now it is", self.ipython_dir) + sys.exit() + self.shadow_hist = ShadowHist(self.shadow_db) + + def save_hist(self): + """Save input history to a file (via readline library).""" + + try: + self.shell.readline.write_history_file(self.hist_file) + except: + print('Unable to save IPython command history to file: ' + + `self.hist_file`) + + def reload_hist(self): + """Reload the input history from disk file.""" + + try: + self.shell.readline.clear_history() + self.shell.readline.read_history_file(self.hist_file) + except AttributeError: + pass + + def get_history(self, index=None, raw=False, output=True): + """Get the history list. + + Get the input and output history. + + Parameters + ---------- + index : n or (n1, n2) or None + If n, then the last entries. If a tuple, then all in + range(n1, n2). If None, then all entries. Raises IndexError if + the format of index is incorrect. + raw : bool + If True, return the raw input. + output : bool + If True, then return the output as well. + + Returns + ------- + If output is True, then return a dict of tuples, keyed by the prompt + numbers and with values of (input, output). If output is False, then + a dict, keyed by the prompt number with the values of input. Raises + IndexError if no history is found. + """ + if raw: + input_hist = self.input_hist_raw + else: + input_hist = self.input_hist + if output: + output_hist = self.output_hist + n = len(input_hist) + if index is None: + start=0; stop=n + elif isinstance(index, int): + start=n-index; stop=n + elif isinstance(index, tuple) and len(index) == 2: + start=index[0]; stop=index[1] + else: + raise IndexError('Not a valid index for the input history: %r' + % index) + hist = {} + for i in range(start, stop): + if output: + hist[i] = (input_hist[i], output_hist.get(i)) + else: + hist[i] = input_hist[i] + if len(hist)==0: + raise IndexError('No history for range of indices: %r' % index) + return hist + def magic_history(self, parameter_s = ''): """Print input history (_i variables), with most recent last. @@ -55,7 +204,7 @@ def magic_history(self, parameter_s = ''): """ if not self.shell.displayhook.do_full_cache: - print 'This feature is only available if numbered prompts are in use.' + print('This feature is only available if numbered prompts are in use.') return opts,args = self.parse_options(parameter_s,'gnoptsrf:',mode='list') @@ -69,7 +218,7 @@ def magic_history(self, parameter_s = ''): else: if os.path.exists(outfname): if not ask_yes_no("File %r exists. Overwrite?" % outfname): - print 'Aborting.' + print('Aborting.') return outfile = open(outfname,'w') @@ -103,7 +252,7 @@ def magic_history(self, parameter_s = ''): init, final = map(int, args) else: warn('%hist takes 0, 1 or 2 arguments separated by spaces.') - print >> IPython.utils.io.Term.cout, self.magic_hist.__doc__ + print(self.magic_hist.__doc__, file=IPython.utils.io.Term.cout) return width = len(str(final)) @@ -117,14 +266,14 @@ def magic_history(self, parameter_s = ''): sh = self.shell.shadowhist.all() for idx, s in sh: if fnmatch.fnmatch(s, pattern): - print >> outfile, "0%d: %s" %(idx, s.expandtabs(4)) + print("0%d: %s" %(idx, s.expandtabs(4)), file=outfile) found = True if found: - print >> outfile, "===" - print >> outfile, \ - "shadow history ends, fetch by %rep (must start with 0)" - print >> outfile, "=== start of normal history ===" + print("===", file=outfile) + print("shadow history ends, fetch by %rep (must start with 0)", + file=outfile) + print("=== start of normal history ===", file=outfile) for in_num in range(init, final): # Print user history with tabs expanded to 4 spaces. The GUI clients @@ -137,22 +286,22 @@ def magic_history(self, parameter_s = ''): multiline = int(inline.count('\n') > 1) if print_nums: - print >> outfile, \ - '%s:%s' % (str(in_num).ljust(width), line_sep[multiline]), + print('%s:%s' % (str(in_num).ljust(width), line_sep[multiline]), + file=outfile) if pyprompts: - print >> outfile, '>>>', + print('>>>', file=outfile) if multiline: lines = inline.splitlines() - print >> outfile, '\n... '.join(lines) - print >> outfile, '... ' + print('\n... '.join(lines), file=outfile) + print('... ', file=outfile) else: - print >> outfile, inline, + print(inline, end='', file=outfile) else: - print >> outfile, inline, + print(inline,end='', file=outfile) if print_outputs: output = self.shell.output_hist.get(in_num) if output is not None: - print >> outfile, repr(output) + print(repr(output), file=outfile) if close_at_end: outfile.close() @@ -223,10 +372,10 @@ def rep_f(self, arg): try: lines = self.extract_input_slices(args, True) - print "lines",lines + print("lines", lines) self.runlines(lines) except ValueError: - print "Not found in recent history:", args + print("Not found in recent history:", args) _sentinel = object() @@ -251,11 +400,11 @@ class ShadowHist(object): if old is not _sentinel: return newidx = self.inc_idx() - #print "new",newidx # dbg + #print("new", newidx) # dbg self.db.hset('shadowhist',ent, newidx) except: ipapi.get().showtraceback() - print "WARNING: disabling shadow history" + print("WARNING: disabling shadow history") self.disabled = True def all(self): @@ -268,7 +417,6 @@ class ShadowHist(object): all = self.all() for k, v in all: - #print k,v if k == idx: return v diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 1ecb6d4..94cbec7 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -45,6 +45,7 @@ from IPython.core.displayhook import DisplayHook from IPython.core.error import TryNext, UsageError from IPython.core.extensions import ExtensionManager from IPython.core.fakemodule import FakeModule, init_fakemod_dict +from IPython.core.history import HistoryManager from IPython.core.inputlist import InputList from IPython.core.inputsplitter import IPythonInputSplitter from IPython.core.logger import Logger @@ -216,7 +217,8 @@ class InteractiveShell(Configurable, Magic): extension_manager = Instance('IPython.core.extensions.ExtensionManager') plugin_manager = Instance('IPython.core.plugin.PluginManager') payload_manager = Instance('IPython.core.payload.PayloadManager') - + history_manager = Instance('IPython.core.history.HistoryManager') + # Private interface _post_execute = set() @@ -261,7 +263,6 @@ class InteractiveShell(Configurable, Magic): self.init_builtins() # pre_config_initialization - self.init_shadow_hist() # The next section should contain everything that was in ipmaker. self.init_logstart() @@ -1211,61 +1212,15 @@ class InteractiveShell(Configurable, Magic): #------------------------------------------------------------------------- def init_history(self): - # List of input with multi-line handling. - self.input_hist = InputList() - # This one will hold the 'raw' input history, without any - # pre-processing. This will allow users to retrieve the input just as - # it was exactly typed in by the user, with %hist -r. - self.input_hist_raw = InputList() - - # list of visited directories - try: - self.dir_hist = [os.getcwd()] - except OSError: - self.dir_hist = [] - - # dict of output history - self.output_hist = {} - - # Now the history file - if self.profile: - histfname = 'history-%s' % self.profile - else: - histfname = 'history' - self.histfile = os.path.join(self.ipython_dir, histfname) - - # Fill the history zero entry, user counter starts at 1 - self.input_hist.append('\n') - self.input_hist_raw.append('\n') - - def init_shadow_hist(self): - try: - self.db = pickleshare.PickleShareDB(self.ipython_dir + "/db") - except exceptions.UnicodeDecodeError: - print "Your ipython_dir can't be decoded to unicode!" - print "Please set HOME environment variable to something that" - print r"only has ASCII characters, e.g. c:\home" - print "Now it is", self.ipython_dir - sys.exit() - self.shadowhist = ipcorehist.ShadowHist(self.db) + self.history_manager = HistoryManager(shell=self) def savehist(self): """Save input history to a file (via readline library).""" - - try: - self.readline.write_history_file(self.histfile) - except: - print 'Unable to save IPython command history to file: ' + \ - `self.histfile` - + self.history_manager.save_hist() + def reloadhist(self): """Reload the input history from disk file.""" - - try: - self.readline.clear_history() - self.readline.read_history_file(self.shell.histfile) - except AttributeError: - pass + self.history_manager.reload_hist() def history_saving_wrapper(self, func): """ Wrap func for readline history saving @@ -1286,55 +1241,6 @@ class InteractiveShell(Configurable, Magic): readline.read_history_file(self.histfile) return wrapper - def get_history(self, index=None, raw=False, output=True): - """Get the history list. - - Get the input and output history. - - Parameters - ---------- - index : n or (n1, n2) or None - If n, then the last entries. If a tuple, then all in - range(n1, n2). If None, then all entries. Raises IndexError if - the format of index is incorrect. - raw : bool - If True, return the raw input. - output : bool - If True, then return the output as well. - - Returns - ------- - If output is True, then return a dict of tuples, keyed by the prompt - numbers and with values of (input, output). If output is False, then - a dict, keyed by the prompt number with the values of input. Raises - IndexError if no history is found. - """ - if raw: - input_hist = self.input_hist_raw - else: - input_hist = self.input_hist - if output: - output_hist = self.user_ns['Out'] - n = len(input_hist) - if index is None: - start=0; stop=n - elif isinstance(index, int): - start=n-index; stop=n - elif isinstance(index, tuple) and len(index) == 2: - start=index[0]; stop=index[1] - else: - raise IndexError('Not a valid index for the input history: %r' - % index) - hist = {} - for i in range(start, stop): - if output: - hist[i] = (input_hist[i], output_hist.get(i)) - else: - hist[i] = input_hist[i] - if len(hist)==0: - raise IndexError('No history for range of indices: %r' % index) - return hist - #------------------------------------------------------------------------- # Things related to exception handling and tracebacks (not debugging) #------------------------------------------------------------------------- @@ -2200,6 +2106,20 @@ class InteractiveShell(Configurable, Magic): self.input_hist_raw.append(cell) self.input_hist.append(ipy_cell) + + # dbg code!!! + def myapp(self, val): # dbg + import traceback as tb + stack = ''.join(tb.format_stack()) + print 'Value:', val + print 'Stack:\n', stack + list.append(self, val) + + import new + self.input_hist.append = new.instancemethod(myapp, self.input_hist, + list) + # End dbg + # All user code execution must happen with our context managers active with nested(self.builtin_trap, self.display_trap): # Single-block input should behave like an interactive prompt