history.py
338 lines
| 12.3 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r6956 | """Implementation of magic functions related to History. | ||
""" | ||||
#----------------------------------------------------------------------------- | ||||
# Copyright (c) 2012, IPython Development Team. | ||||
# | ||||
# Distributed under the terms of the Modified BSD License. | ||||
# | ||||
# The full license is in the file COPYING.txt, distributed with this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
# Stdlib | ||||
import os | ||||
Thomas Kluyver
|
r22192 | import sys | ||
Fernando Perez
|
r6956 | from io import open as io_open | ||
Daniel Goldfarb
|
r26363 | import fnmatch | ||
Fernando Perez
|
r6956 | |||
# Our own packages | ||||
from IPython.core.error import StdinNotImplementedError | ||||
Fernando Perez
|
r6973 | from IPython.core.magic import Magics, magics_class, line_magic | ||
Takafumi Arakaki
|
r8539 | from IPython.core.magic_arguments import (argument, magic_arguments, | ||
Takafumi Arakaki
|
r8424 | parse_argstring) | ||
Fernando Perez
|
r6956 | from IPython.testing.skipdoctest import skip_doctest | ||
from IPython.utils import io | ||||
#----------------------------------------------------------------------------- | ||||
# Magics class implementation | ||||
#----------------------------------------------------------------------------- | ||||
Takafumi Arakaki
|
r8424 | |||
Takafumi Arakaki
|
r8539 | _unspecified = object() | ||
Takafumi Arakaki
|
r8424 | |||
Fernando Perez
|
r6973 | @magics_class | ||
Fernando Perez
|
r6956 | class HistoryMagics(Magics): | ||
Takafumi Arakaki
|
r8424 | @magic_arguments() | ||
@argument( | ||||
'-n', dest='print_nums', action='store_true', default=False, | ||||
help=""" | ||||
print line numbers for each input. | ||||
This feature is only available if numbered prompts are in use. | ||||
""") | ||||
@argument( | ||||
'-o', dest='get_output', action='store_true', default=False, | ||||
help="also print outputs for each input.") | ||||
@argument( | ||||
'-p', dest='pyprompts', action='store_true', default=False, | ||||
help=""" | ||||
print classic '>>>' python prompts before each input. | ||||
This is useful for making documentation, and in conjunction | ||||
with -o, for producing doctest-ready output. | ||||
""") | ||||
@argument( | ||||
'-t', dest='raw', action='store_false', default=True, | ||||
help=""" | ||||
print the 'translated' history, as IPython understands it. | ||||
IPython filters your input and converts it all into valid Python | ||||
source before executing it (things like magics or aliases are turned | ||||
into function calls, for example). With this option, you'll see the | ||||
native history instead of the user-entered version: '%%cd /' will be | ||||
adityausathe
|
r23750 | seen as 'get_ipython().run_line_magic("cd", "/")' instead of '%%cd /'. | ||
Takafumi Arakaki
|
r8424 | """) | ||
@argument( | ||||
'-f', dest='filename', | ||||
help=""" | ||||
FILENAME: instead of printing the output to the screen, redirect | ||||
it to the given file. The file is always overwritten, though *when | ||||
it can*, IPython asks for confirmation first. In particular, running | ||||
the command 'history -f FILENAME' from the IPython Notebook | ||||
interface will replace FILENAME even if it already exists *without* | ||||
confirmation. | ||||
""") | ||||
@argument( | ||||
'-g', dest='pattern', nargs='*', default=None, | ||||
help=""" | ||||
Takafumi Arakaki
|
r8452 | treat the arg as a glob pattern to search for in (full) history. | ||
Takafumi Arakaki
|
r8424 | This includes the saved history (almost all commands ever written). | ||
The pattern may contain '?' to match one unknown character and '*' | ||||
to match any number of unknown characters. Use '%%hist -g' to show | ||||
full saved history (may be very long). | ||||
""") | ||||
@argument( | ||||
Takafumi Arakaki
|
r8539 | '-l', dest='limit', type=int, nargs='?', default=_unspecified, | ||
Takafumi Arakaki
|
r8424 | help=""" | ||
get the last n lines from all sessions. Specify n as a single | ||||
arg, or the default is the last 10 lines. | ||||
""") | ||||
Takafumi Arakaki
|
r8784 | @argument( | ||
'-u', dest='unique', action='store_true', | ||||
help=""" | ||||
when searching history using `-g`, show only unique history. | ||||
""") | ||||
Takafumi Arakaki
|
r8424 | @argument('range', nargs='*') | ||
Fernando Perez
|
r6956 | @skip_doctest | ||
@line_magic | ||||
def history(self, parameter_s = ''): | ||||
"""Print input history (_i<n> variables), with most recent last. | ||||
By default, input history is printed without line numbers so it can be | ||||
directly pasted into an editor. Use -n to show them. | ||||
By default, all input history from the current session is displayed. | ||||
Ranges of history can be indicated using the syntax: | ||||
Matthias Bussonnier
|
r27294 | |||
Thomas Kluyver
|
r9244 | ``4`` | ||
Line 4, current session | ||||
``4-6`` | ||||
Lines 4-6, current session | ||||
``243/1-5`` | ||||
Lines 1-5, session 243 | ||||
``~2/7`` | ||||
Line 7, session 2 before current | ||||
``~8/1-~6/5`` | ||||
From the first line of 8 sessions ago, to the fifth line of 6 | ||||
sessions ago. | ||||
Matthias Bussonnier
|
r27294 | |||
Fernando Perez
|
r6956 | Multiple ranges can be entered, separated by spaces | ||
The same syntax is used by %macro, %save, %edit, %rerun | ||||
Examples | ||||
-------- | ||||
:: | ||||
Bradley M. Froehle
|
r7933 | In [6]: %history -n 4-6 | ||
Fernando Perez
|
r6956 | 4:a = 12 | ||
Antony Lee
|
r28756 | 5:print(a**2) | ||
Bradley M. Froehle
|
r7933 | 6:%history -n 4-6 | ||
Fernando Perez
|
r6956 | |||
""" | ||||
Takafumi Arakaki
|
r8424 | args = parse_argstring(self.history, parameter_s) | ||
Fernando Perez
|
r6956 | |||
# For brevity | ||||
history_manager = self.shell.history_manager | ||||
def _format_lineno(session, line): | ||||
"""Helper function to format line numbers properly.""" | ||||
if session in (0, history_manager.session_number): | ||||
return str(line) | ||||
return "%s/%s" % (session, line) | ||||
# Check if output to specific file was requested. | ||||
Takafumi Arakaki
|
r8424 | outfname = args.filename | ||
if not outfname: | ||||
Thomas Kluyver
|
r22192 | outfile = sys.stdout # default | ||
Fernando Perez
|
r6956 | # We don't want to close stdout at the end! | ||
close_at_end = False | ||||
else: | ||||
nicolaslazo
|
r27282 | outfname = os.path.expanduser(outfname) | ||
Fernando Perez
|
r6956 | if os.path.exists(outfname): | ||
try: | ||||
ans = io.ask_yes_no("File %r exists. Overwrite?" % outfname) | ||||
except StdinNotImplementedError: | ||||
ans = True | ||||
if not ans: | ||||
print('Aborting.') | ||||
return | ||||
print("Overwriting file.") | ||||
outfile = io_open(outfname, 'w', encoding='utf-8') | ||||
close_at_end = True | ||||
Takafumi Arakaki
|
r8424 | print_nums = args.print_nums | ||
get_output = args.get_output | ||||
pyprompts = args.pyprompts | ||||
raw = args.raw | ||||
Fernando Perez
|
r6956 | |||
pattern = None | ||||
Takafumi Arakaki
|
r8539 | limit = None if args.limit is _unspecified else args.limit | ||
Fernando Perez
|
r6956 | |||
Daniel Goldfarb
|
r26363 | range_pattern = False | ||
if args.pattern is not None and not args.range: | ||||
Takafumi Arakaki
|
r8424 | if args.pattern: | ||
pattern = "*" + " ".join(args.pattern) + "*" | ||||
else: | ||||
pattern = "*" | ||||
Takafumi Arakaki
|
r8425 | hist = history_manager.search(pattern, raw=raw, output=get_output, | ||
Takafumi Arakaki
|
r8784 | n=limit, unique=args.unique) | ||
Fernando Perez
|
r6956 | print_nums = True | ||
Takafumi Arakaki
|
r8539 | elif args.limit is not _unspecified: | ||
n = 10 if limit is None else limit | ||||
Fernando Perez
|
r6956 | hist = history_manager.get_tail(n, raw=raw, output=get_output) | ||
else: | ||||
Blazej Michalik
|
r26632 | if args.pattern: | ||
range_pattern = "*" + " ".join(args.pattern) + "*" | ||||
print_nums = True | ||||
Blazej Michalik
|
r26641 | hist = history_manager.get_range_by_str( | ||
" ".join(args.range), raw, get_output | ||||
) | ||||
Fernando Perez
|
r6956 | |||
# We could be displaying the entire history, so let's not try to pull | ||||
# it into a list in memory. Anything that needs more space will just | ||||
# misalign. | ||||
width = 4 | ||||
for session, lineno, inline in hist: | ||||
# Print user history with tabs expanded to 4 spaces. The GUI | ||||
# clients use hard tabs for easier usability in auto-indented code, | ||||
# but we want to produce PEP-8 compliant history for safe pasting | ||||
# into an editor. | ||||
if get_output: | ||||
inline, output = inline | ||||
Daniel Goldfarb
|
r26363 | if range_pattern: | ||
Daniel Goldfarb
|
r26365 | if not fnmatch.fnmatch(inline, range_pattern): | ||
continue | ||||
Fernando Perez
|
r6956 | inline = inline.expandtabs(4).rstrip() | ||
multiline = "\n" in inline | ||||
line_sep = '\n' if multiline else ' ' | ||||
if print_nums: | ||||
print(u'%s:%s' % (_format_lineno(session, lineno).rjust(width), | ||||
line_sep), file=outfile, end=u'') | ||||
if pyprompts: | ||||
print(u">>> ", end=u"", file=outfile) | ||||
if multiline: | ||||
inline = "\n... ".join(inline.splitlines()) + "\n..." | ||||
print(inline, file=outfile) | ||||
if get_output and output: | ||||
Srinivas Reddy Thatiparthy
|
r23669 | print(output, file=outfile) | ||
Fernando Perez
|
r6956 | |||
if close_at_end: | ||||
outfile.close() | ||||
@line_magic | ||||
Bradley M. Froehle
|
r7933 | def recall(self, arg): | ||
Fernando Perez
|
r6956 | r"""Repeat a command, or get command to input line for editing. | ||
%recall and %rep are equivalent. | ||||
- %recall (no arguments): | ||||
Place a string version of last computation result (stored in the | ||||
special '_' variable) to the next input prompt. Allows you to create | ||||
elaborate command lines without using copy-paste:: | ||||
In[1]: l = ["hei", "vaan"] | ||||
In[2]: "".join(l) | ||||
Out[2]: heivaan | ||||
Bradley M. Froehle
|
r7933 | In[3]: %recall | ||
Fernando Perez
|
r6956 | In[4]: heivaan_ <== cursor blinking | ||
%recall 45 | ||||
Place history line 45 on the next input prompt. Use %hist to find | ||||
out the number. | ||||
%recall 1-4 | ||||
Combine the specified lines into one cell, and place it on the next | ||||
input prompt. See %history for the slice syntax. | ||||
%recall foo+bar | ||||
If foo+bar can be evaluated in the user namespace, the result is | ||||
placed at the next input prompt. Otherwise, the history is searched | ||||
for lines which contain that substring, and the most recent one is | ||||
placed at the next input prompt. | ||||
""" | ||||
if not arg: # Last output | ||||
self.shell.set_next_input(str(self.shell.user_ns["_"])) | ||||
return | ||||
# Get history range | ||||
histlines = self.shell.history_manager.get_range_by_str(arg) | ||||
cmd = "\n".join(x[2] for x in histlines) | ||||
if cmd: | ||||
self.shell.set_next_input(cmd.rstrip()) | ||||
return | ||||
try: # Variable in user namespace | ||||
cmd = str(eval(arg, self.shell.user_ns)) | ||||
except Exception: # Search for term in history | ||||
histlines = self.shell.history_manager.search("*"+arg+"*") | ||||
for h in reversed([x[2] for x in histlines]): | ||||
Bradley M. Froehle
|
r7933 | if 'recall' in h or 'rep' in h: | ||
Fernando Perez
|
r6956 | continue | ||
self.shell.set_next_input(h.rstrip()) | ||||
return | ||||
else: | ||||
self.shell.set_next_input(cmd.rstrip()) | ||||
Blazej Michalik
|
r26533 | return | ||
Fernando Perez
|
r6956 | print("Couldn't evaluate or find in history:", arg) | ||
@line_magic | ||||
def rerun(self, parameter_s=''): | ||||
"""Re-run previous input | ||||
By default, you can specify ranges of input history to be repeated | ||||
(as with %history). With no arguments, it will repeat the last line. | ||||
Options: | ||||
-l <n> : Repeat the last n lines of input, not including the | ||||
current command. | ||||
-g foo : Repeat the most recent line which contains foo | ||||
""" | ||||
opts, args = self.parse_options(parameter_s, 'l:g:', mode='string') | ||||
if "l" in opts: # Last n lines | ||||
nicolaslazo
|
r27245 | try: | ||
n = int(opts["l"]) | ||||
except ValueError: | ||||
print("Number of lines must be an integer") | ||||
return | ||||
Blazej Michalik
|
r26549 | |||
if n == 0: | ||||
print("Requested 0 last lines - nothing to run") | ||||
return | ||||
elif n < 0: | ||||
print("Number of lines to rerun cannot be negative") | ||||
return | ||||
Fernando Perez
|
r6956 | hist = self.shell.history_manager.get_tail(n) | ||
elif "g" in opts: # Search | ||||
p = "*"+opts['g']+"*" | ||||
hist = list(self.shell.history_manager.search(p)) | ||||
for l in reversed(hist): | ||||
if "rerun" not in l[2]: | ||||
hist = [l] # The last match which isn't a %rerun | ||||
break | ||||
else: | ||||
hist = [] # No matches except %rerun | ||||
elif args: # Specify history ranges | ||||
hist = self.shell.history_manager.get_range_by_str(args) | ||||
else: # Last line | ||||
hist = self.shell.history_manager.get_tail(1) | ||||
hist = [x[2] for x in hist] | ||||
if not hist: | ||||
print("No lines in history match specification") | ||||
return | ||||
histlines = "\n".join(hist) | ||||
print("=== Executing: ===") | ||||
print(histlines) | ||||
print("=== Output: ===") | ||||
self.shell.run_cell("\n".join(hist), store_history=False) | ||||