##// END OF EJS Templates
Add error handling so SQLite history can recover from corrupt databases and session/line number collisions.
Thomas Kluyver -
Show More
@@ -1,709 +1,740 b''
1 1 """ History related magics and functionality """
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2010 The IPython Development Team.
4 4 #
5 5 # Distributed under the terms of the BSD License.
6 6 #
7 7 # The full license is in the file COPYING.txt, distributed with this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 from __future__ import print_function
14 14
15 15 # Stdlib imports
16 16 import datetime
17 17 import json
18 18 import os
19 19 import re
20 20 import sqlite3
21 21
22 22 from collections import defaultdict
23 23
24 24 # Our own packages
25 25 from IPython.config.configurable import Configurable
26 26 import IPython.utils.io
27 27
28 28 from IPython.testing import decorators as testdec
29 29 from IPython.utils.io import ask_yes_no
30 30 from IPython.utils.traitlets import Bool, Dict, Instance, Int, List, Unicode
31 31 from IPython.utils.warn import warn
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # Classes and functions
35 35 #-----------------------------------------------------------------------------
36 36
37 37 class HistoryManager(Configurable):
38 38 """A class to organize all history-related functionality in one place.
39 39 """
40 40 # Public interface
41 41
42 42 # An instance of the IPython shell we are attached to
43 43 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
44 44 # Lists to hold processed and raw history. These start with a blank entry
45 45 # so that we can index them starting from 1
46 46 input_hist_parsed = List([""])
47 47 input_hist_raw = List([""])
48 48 # A list of directories visited during session
49 49 dir_hist = List()
50 50 # A dict of output history, keyed with ints from the shell's
51 51 # execution count. If there are several outputs from one command,
52 52 # only the last one is stored.
53 53 output_hist = Dict()
54 54 # Contains all outputs, in lists of reprs.
55 55 output_hist_reprs = Instance(defaultdict)
56 56
57 57 # String holding the path to the history file
58 58 hist_file = Unicode()
59 59 # The SQLite database
60 60 db = Instance(sqlite3.Connection)
61 61 # The number of the current session in the history database
62 62 session_number = Int()
63 63 # Should we log output to the database? (default no)
64 64 db_log_output = Bool(False, config=True)
65 65 # Write to database every x commands (higher values save disk access & power)
66 66 # Values of 1 or less effectively disable caching.
67 67 db_cache_size = Int(0, config=True)
68 68 # The input and output caches
69 69 db_input_cache = List()
70 70 db_output_cache = List()
71 71
72 72 # Private interface
73 73 # Variables used to store the three last inputs from the user. On each new
74 74 # history update, we populate the user's namespace with these, shifted as
75 75 # necessary.
76 76 _i00, _i, _ii, _iii = '','','',''
77 77
78 78 # A set with all forms of the exit command, so that we don't store them in
79 79 # the history (it's annoying to rewind the first entry and land on an exit
80 80 # call).
81 81 _exit_commands = None
82 82
83 83 def __init__(self, shell, config=None):
84 84 """Create a new history manager associated with a shell instance.
85 85 """
86 86 # We need a pointer back to the shell for various tasks.
87 87 super(HistoryManager, self).__init__(shell=shell, config=config)
88 88
89 89 # list of visited directories
90 90 try:
91 91 self.dir_hist = [os.getcwd()]
92 92 except OSError:
93 93 self.dir_hist = []
94 94
95 95 # Now the history file
96 96 if shell.profile:
97 97 histfname = 'history-%s' % shell.profile
98 98 else:
99 99 histfname = 'history'
100 100 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.sqlite')
101 self.init_db()
101 try:
102 self.init_db()
103 except sqlite3.DatabaseError:
104 newpath = os.path.join(self.shell.ipython_dir, "hist-corrupt.sqlite")
105 os.rename(self.hist_file, newpath)
106 print("ERROR! History file wasn't a valid SQLite database.",
107 "It was moved to %s" % newpath, "and a new file created.")
108 self.init_db()
109
102 110 self.new_session()
103 111
104 112 self._i00, self._i, self._ii, self._iii = '','','',''
105 113 self.output_hist_reprs = defaultdict(list)
106 114
107 115 self._exit_commands = set(['Quit', 'quit', 'Exit', 'exit', '%Quit',
108 116 '%quit', '%Exit', '%exit'])
109 117
110 118 def init_db(self):
111 119 """Connect to the database, and create tables if necessary."""
112 120 self.db = sqlite3.connect(self.hist_file)
113 121 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
114 122 primary key autoincrement, start timestamp,
115 123 end timestamp, num_cmds integer, remark text)""")
116 124 self.db.execute("""CREATE TABLE IF NOT EXISTS history
117 125 (session integer, line integer, source text, source_raw text,
118 126 PRIMARY KEY (session, line))""")
119 127 # Output history is optional, but ensure the table's there so it can be
120 128 # enabled later.
121 129 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
122 130 (session integer, line integer, output text,
123 131 PRIMARY KEY (session, line))""")
124 132 self.db.commit()
125 133
126 134 def new_session(self):
127 135 """Get a new session number."""
128 136 with self.db:
129 137 cur = self.db.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
130 138 NULL, "") """, (datetime.datetime.now(),))
131 139 self.session_number = cur.lastrowid
132 140
133 141 def end_session(self):
134 142 """Close the database session, filling in the end time and line count."""
135 143 self.writeout_cache()
136 144 with self.db:
137 145 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
138 146 session==?""", (datetime.datetime.now(),
139 147 len(self.input_hist_parsed)-1, self.session_number))
140 148 self.session_number = 0
141 149
142 150 def name_session(self, name):
143 151 """Give the current session a name in the history database."""
144 152 with self.db:
145 153 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
146 154 (name, self.session_number))
147 155
148 156 def reset(self, new_session=True):
149 157 """Clear the session history, releasing all object references, and
150 158 optionally open a new session."""
151 159 if self.session_number:
152 160 self.end_session()
153 161 self.input_hist_parsed[:] = [""]
154 162 self.input_hist_raw[:] = [""]
155 163 self.output_hist.clear()
156 164 # The directory history can't be completely empty
157 165 self.dir_hist[:] = [os.getcwd()]
158 166
159 167 if new_session:
160 168 self.new_session()
161 169
162 170 ## -------------------------------
163 171 ## Methods for retrieving history:
164 172 ## -------------------------------
165 173 def _run_sql(self, sql, params, raw=True, output=False):
166 174 """Prepares and runs an SQL query for the history database.
167 175
168 176 Parameters
169 177 ----------
170 178 sql : str
171 179 Any filtering expressions to go after SELECT ... FROM ...
172 180 params : tuple
173 181 Parameters passed to the SQL query (to replace "?")
174 182 raw, output : bool
175 183 See :meth:`get_range`
176 184
177 185 Returns
178 186 -------
179 187 Tuples as :meth:`get_range`
180 188 """
181 189 toget = 'source_raw' if raw else 'source'
182 190 sqlfrom = "history"
183 191 if output:
184 192 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
185 193 toget = "history.%s, output_history.output" % toget
186 194 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
187 195 (toget, sqlfrom) + sql, params)
188 196 if output: # Regroup into 3-tuples, and parse JSON
189 197 loads = lambda out: json.loads(out) if out else None
190 198 return ((ses, lin, (inp, loads(out))) \
191 199 for ses, lin, inp, out in cur)
192 200 return cur
193 201
194 202
195 203 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
196 204 """Get the last n lines from the history database.
197 205
198 206 Parameters
199 207 ----------
200 208 n : int
201 209 The number of lines to get
202 210 raw, output : bool
203 211 See :meth:`get_range`
204 212 include_latest : bool
205 213 If False (default), n+1 lines are fetched, and the latest one
206 214 is discarded. This is intended to be used where the function
207 215 is called by a user command, which it should not return.
208 216
209 217 Returns
210 218 -------
211 219 Tuples as :meth:`get_range`
212 220 """
213 221 self.writeout_cache()
214 222 if not include_latest:
215 223 n += 1
216 224 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
217 225 (n,), raw=raw, output=output)
218 226 if not include_latest:
219 227 return reversed(list(cur)[1:])
220 228 return reversed(list(cur))
221 229
222 230 def search(self, pattern="*", raw=True, search_raw=True,
223 231 output=False):
224 232 """Search the database using unix glob-style matching (wildcards
225 233 * and ?).
226 234
227 235 Parameters
228 236 ----------
229 237 pattern : str
230 238 The wildcarded pattern to match when searching
231 239 search_raw : bool
232 240 If True, search the raw input, otherwise, the parsed input
233 241 raw, output : bool
234 242 See :meth:`get_range`
235 243
236 244 Returns
237 245 -------
238 246 Tuples as :meth:`get_range`
239 247 """
240 248 tosearch = "source_raw" if search_raw else "source"
241 249 if output:
242 250 tosearch = "history." + tosearch
243 251 self.writeout_cache()
244 252 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
245 253 raw=raw, output=output)
246 254
247 255 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
248 256 """Get input and output history from the current session. Called by
249 257 get_range, and takes similar parameters."""
250 258 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
251 259
252 260 n = len(input_hist)
253 261 if start < 0:
254 262 start += n
255 263 if not stop:
256 264 stop = n
257 265 elif stop < 0:
258 266 stop += n
259 267
260 268 for i in range(start, stop):
261 269 if output:
262 270 line = (input_hist[i], self.output_hist_reprs.get(i))
263 271 else:
264 272 line = input_hist[i]
265 273 yield (0, i, line)
266 274
267 275 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
268 276 """Retrieve input by session.
269 277
270 278 Parameters
271 279 ----------
272 280 session : int
273 281 Session number to retrieve. The current session is 0, and negative
274 282 numbers count back from current session, so -1 is previous session.
275 283 start : int
276 284 First line to retrieve.
277 285 stop : int
278 286 End of line range (excluded from output itself). If None, retrieve
279 287 to the end of the session.
280 288 raw : bool
281 289 If True, return untranslated input
282 290 output : bool
283 291 If True, attempt to include output. This will be 'real' Python
284 292 objects for the current session, or text reprs from previous
285 293 sessions if db_log_output was enabled at the time. Where no output
286 294 is found, None is used.
287 295
288 296 Returns
289 297 -------
290 298 An iterator over the desired lines. Each line is a 3-tuple, either
291 299 (session, line, input) if output is False, or
292 300 (session, line, (input, output)) if output is True.
293 301 """
294 302 if session == 0 or session==self.session_number: # Current session
295 303 return self._get_range_session(start, stop, raw, output)
296 304 if session < 0:
297 305 session += self.session_number
298 306
299 307 if stop:
300 308 lineclause = "line >= ? AND line < ?"
301 309 params = (session, start, stop)
302 310 else:
303 311 lineclause = "line>=?"
304 312 params = (session, start)
305 313
306 314 return self._run_sql("WHERE session==? AND %s""" % lineclause,
307 315 params, raw=raw, output=output)
308 316
309 317 def get_range_by_str(self, rangestr, raw=True, output=False):
310 318 """Get lines of history from a string of ranges, as used by magic
311 319 commands %hist, %save, %macro, etc.
312 320
313 321 Parameters
314 322 ----------
315 323 rangestr : str
316 324 A string specifying ranges, e.g. "5 ~2/1-4". See
317 325 :func:`magic_history` for full details.
318 326 raw, output : bool
319 327 As :meth:`get_range`
320 328
321 329 Returns
322 330 -------
323 331 Tuples as :meth:`get_range`
324 332 """
325 333 for sess, s, e in extract_hist_ranges(rangestr):
326 334 for line in self.get_range(sess, s, e, raw=raw, output=output):
327 335 yield line
328 336
329 337 ## ----------------------------
330 338 ## Methods for storing history:
331 339 ## ----------------------------
332 340 def store_inputs(self, line_num, source, source_raw=None):
333 341 """Store source and raw input in history and create input cache
334 342 variables _i*.
335 343
336 344 Parameters
337 345 ----------
338 346 line_num : int
339 347 The prompt number of this input.
340 348
341 349 source : str
342 350 Python input.
343 351
344 352 source_raw : str, optional
345 353 If given, this is the raw input without any IPython transformations
346 354 applied to it. If not given, ``source`` is used.
347 355 """
348 356 if source_raw is None:
349 357 source_raw = source
350 358 source = source.rstrip('\n')
351 359 source_raw = source_raw.rstrip('\n')
352 360
353 361 # do not store exit/quit commands
354 362 if source_raw.strip() in self._exit_commands:
355 363 return
356 364
357 365 self.input_hist_parsed.append(source)
358 366 self.input_hist_raw.append(source_raw)
359 367
360 self.db_input_cache.append((self.session_number, line_num,
361 source, source_raw))
368 self.db_input_cache.append((line_num, source, source_raw))
362 369 # Trigger to flush cache and write to DB.
363 370 if len(self.db_input_cache) >= self.db_cache_size:
364 371 self.writeout_cache()
365 372
366 373 # update the auto _i variables
367 374 self._iii = self._ii
368 375 self._ii = self._i
369 376 self._i = self._i00
370 377 self._i00 = source_raw
371 378
372 379 # hackish access to user namespace to create _i1,_i2... dynamically
373 380 new_i = '_i%s' % line_num
374 381 to_main = {'_i': self._i,
375 382 '_ii': self._ii,
376 383 '_iii': self._iii,
377 384 new_i : self._i00 }
378 385 self.shell.user_ns.update(to_main)
379 386
380 387 def store_output(self, line_num):
381 388 """If database output logging is enabled, this saves all the
382 389 outputs from the indicated prompt number to the database. It's
383 390 called by run_cell after code has been executed.
384 391
385 392 Parameters
386 393 ----------
387 394 line_num : int
388 395 The line number from which to save outputs
389 396 """
390 397 if (not self.db_log_output) or not self.output_hist_reprs[line_num]:
391 398 return
392 399 output = json.dumps(self.output_hist_reprs[line_num])
393 db_row = (self.session_number, line_num, output)
394 if self.db_cache_size > 1:
395 self.db_output_cache.append(db_row)
396 else:
397 with self.db:
398 self.db.execute("INSERT INTO output_history VALUES (?,?,?)", db_row)
399 400
401 self.db_output_cache.append((line_num, output))
402 if self.db_cache_size <= 1:
403 self.writeout_cache()
404
405 def _writeout_input_cache(self):
406 for line in self.db_input_cache:
407 with self.db:
408 self.db.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
409 (self.session_number,)+line)
410
411 def _writeout_output_cache(self):
412 for line in self.db_output_cache:
413 with self.db:
414 self.db.execute("INSERT INTO output_history VALUES (?, ?, ?)",
415 (self.session_number,)+line)
416
400 417 def writeout_cache(self):
401 #print(self.db_input_cache)
402 with self.db:
403 self.db.executemany("INSERT INTO history VALUES (?, ?, ?, ?)",
404 self.db_input_cache)
405 self.db.executemany("INSERT INTO output_history VALUES (?, ?, ?)",
406 self.db_output_cache)
407 self.db_input_cache = []
408 self.db_output_cache = []
418 """Write any entries in the cache to the database."""
419 try:
420 self._writeout_input_cache()
421 except sqlite3.IntegrityError:
422 self.new_session()
423 print("ERROR! Session/line number was not unique in",
424 "database. History logging moved to new session",
425 self.session_number)
426 try: # Try writing to the new session. If this fails, don't recurse
427 self.writeout_cache()
428 except sqlite3.IntegrityError:
429 pass
430 finally:
431 self.db_input_cache = []
432
433 try:
434 self._writeout_output_cache()
435 except sqlite3.IntegrityError:
436 print("!! Session/line number for output was not unique",
437 "in database. Output will not be stored.")
438 finally:
439 self.db_output_cache = []
409 440
410 441
411 442 # To match, e.g. ~5/8-~2/3
412 443 range_re = re.compile(r"""
413 444 ((?P<startsess>~?\d+)/)?
414 445 (?P<start>\d+) # Only the start line num is compulsory
415 446 ((?P<sep>[\-:])
416 447 ((?P<endsess>~?\d+)/)?
417 448 (?P<end>\d+))?
418 449 """, re.VERBOSE)
419 450
420 451 def extract_hist_ranges(ranges_str):
421 452 """Turn a string of history ranges into 3-tuples of (session, start, stop).
422 453
423 454 Examples
424 455 --------
425 456 list(extract_input_ranges("~8/5-~7/4 2"))
426 457 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
427 458 """
428 459 for range_str in ranges_str.split():
429 460 rmatch = range_re.match(range_str)
430 461 if not rmatch:
431 462 continue
432 463 start = int(rmatch.group("start"))
433 464 end = rmatch.group("end")
434 465 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
435 466 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
436 467 end += 1
437 468 startsess = rmatch.group("startsess") or "0"
438 469 endsess = rmatch.group("endsess") or startsess
439 470 startsess = int(startsess.replace("~","-"))
440 471 endsess = int(endsess.replace("~","-"))
441 472 assert endsess >= startsess
442 473
443 474 if endsess == startsess:
444 475 yield (startsess, start, end)
445 476 continue
446 477 # Multiple sessions in one range:
447 478 yield (startsess, start, None)
448 479 for sess in range(startsess+1, endsess):
449 480 yield (sess, 1, None)
450 481 yield (endsess, 1, end)
451 482
452 483 def _format_lineno(session, line):
453 484 """Helper function to format line numbers properly."""
454 485 if session == 0:
455 486 return str(line)
456 487 return "%s#%s" % (session, line)
457 488
458 489 @testdec.skip_doctest
459 490 def magic_history(self, parameter_s = ''):
460 491 """Print input history (_i<n> variables), with most recent last.
461 492
462 493 %history -> print at most 40 inputs (some may be multi-line)\\
463 494 %history n -> print at most n inputs\\
464 495 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
465 496
466 497 By default, input history is printed without line numbers so it can be
467 498 directly pasted into an editor. Use -n to show them.
468 499
469 500 Ranges of history can be indicated using the syntax:
470 501 4 : Line 4, current session
471 502 4-6 : Lines 4-6, current session
472 503 243/1-5: Lines 1-5, session 243
473 504 ~2/7 : Line 7, session 2 before current
474 505 ~8/1-~6/5 : From the first line of 8 sessions ago, to the fifth line
475 506 of 6 sessions ago.
476 507 Multiple ranges can be entered, separated by spaces
477 508
478 509 The same syntax is used by %macro, %save, %edit, %rerun
479 510
480 511 Options:
481 512
482 513 -n: print line numbers for each input.
483 514 This feature is only available if numbered prompts are in use.
484 515
485 516 -o: also print outputs for each input.
486 517
487 518 -p: print classic '>>>' python prompts before each input. This is useful
488 519 for making documentation, and in conjunction with -o, for producing
489 520 doctest-ready output.
490 521
491 522 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
492 523
493 524 -t: print the 'translated' history, as IPython understands it. IPython
494 525 filters your input and converts it all into valid Python source before
495 526 executing it (things like magics or aliases are turned into function
496 527 calls, for example). With this option, you'll see the native history
497 528 instead of the user-entered version: '%cd /' will be seen as
498 529 'get_ipython().magic("%cd /")' instead of '%cd /'.
499 530
500 531 -g: treat the arg as a pattern to grep for in (full) history.
501 532 This includes the saved history (almost all commands ever written).
502 533 Use '%hist -g' to show full saved history (may be very long).
503 534
504 535 -l: get the last n lines from all sessions. Specify n as a single arg, or
505 536 the default is the last 10 lines.
506 537
507 538 -f FILENAME: instead of printing the output to the screen, redirect it to
508 539 the given file. The file is always overwritten, though IPython asks for
509 540 confirmation first if it already exists.
510 541
511 542 Examples
512 543 --------
513 544 ::
514 545
515 546 In [6]: %hist -n 4 6
516 547 4:a = 12
517 548 5:print a**2
518 549
519 550 """
520 551
521 552 if not self.shell.displayhook.do_full_cache:
522 553 print('This feature is only available if numbered prompts are in use.')
523 554 return
524 555 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
525 556
526 557 # For brevity
527 558 history_manager = self.shell.history_manager
528 559
529 560 def _format_lineno(session, line):
530 561 """Helper function to format line numbers properly."""
531 562 if session in (0, history_manager.session_number):
532 563 return str(line)
533 564 return "%s/%s" % (session, line)
534 565
535 566 # Check if output to specific file was requested.
536 567 try:
537 568 outfname = opts['f']
538 569 except KeyError:
539 570 outfile = IPython.utils.io.Term.cout # default
540 571 # We don't want to close stdout at the end!
541 572 close_at_end = False
542 573 else:
543 574 if os.path.exists(outfname):
544 575 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
545 576 print('Aborting.')
546 577 return
547 578
548 579 outfile = open(outfname,'w')
549 580 close_at_end = True
550 581
551 582 print_nums = 'n' in opts
552 583 get_output = 'o' in opts
553 584 pyprompts = 'p' in opts
554 585 # Raw history is the default
555 586 raw = not('t' in opts)
556 587
557 588 default_length = 40
558 589 pattern = None
559 590
560 591 if 'g' in opts: # Glob search
561 592 pattern = "*" + args + "*" if args else "*"
562 593 hist = history_manager.search(pattern, raw=raw, output=get_output)
563 594 elif 'l' in opts: # Get 'tail'
564 595 try:
565 596 n = int(args)
566 597 except ValueError, IndexError:
567 598 n = 10
568 599 hist = history_manager.get_tail(n, raw=raw, output=get_output)
569 600 else:
570 601 if args: # Get history by ranges
571 602 hist = history_manager.get_range_by_str(args, raw, get_output)
572 603 else: # Just get history for the current session
573 604 hist = history_manager.get_range(raw=raw, output=get_output)
574 605
575 606 # We could be displaying the entire history, so let's not try to pull it
576 607 # into a list in memory. Anything that needs more space will just misalign.
577 608 width = 4
578 609
579 610 for session, lineno, inline in hist:
580 611 # Print user history with tabs expanded to 4 spaces. The GUI clients
581 612 # use hard tabs for easier usability in auto-indented code, but we want
582 613 # to produce PEP-8 compliant history for safe pasting into an editor.
583 614 if get_output:
584 615 inline, output = inline
585 616 inline = inline.expandtabs(4).rstrip()
586 617
587 618 multiline = "\n" in inline
588 619 line_sep = '\n' if multiline else ' '
589 620 if print_nums:
590 621 print('%s:%s' % (_format_lineno(session, lineno).rjust(width),
591 622 line_sep), file=outfile, end='')
592 623 if pyprompts:
593 624 print(">>> ", end="", file=outfile)
594 625 if multiline:
595 626 inline = "\n... ".join(inline.splitlines()) + "\n..."
596 627 print(inline, file=outfile)
597 628 if get_output and output:
598 629 print("\n".join(output), file=outfile)
599 630
600 631 if close_at_end:
601 632 outfile.close()
602 633
603 634
604 635 def magic_rep(self, arg):
605 636 r""" Repeat a command, or get command to input line for editing
606 637
607 638 - %rep (no arguments):
608 639
609 640 Place a string version of last computation result (stored in the special '_'
610 641 variable) to the next input prompt. Allows you to create elaborate command
611 642 lines without using copy-paste::
612 643
613 644 In[1]: l = ["hei", "vaan"]
614 645 In[2]: "".join(l)
615 646 Out[2]: heivaan
616 647 In[3]: %rep
617 648 In[4]: heivaan_ <== cursor blinking
618 649
619 650 %rep 45
620 651
621 652 Place history line 45 on the next input prompt. Use %hist to find
622 653 out the number.
623 654
624 655 %rep 1-4
625 656
626 657 Combine the specified lines into one cell, and place it on the next
627 658 input prompt. See %history for the slice syntax.
628 659
629 660 %rep foo+bar
630 661
631 662 If foo+bar can be evaluated in the user namespace, the result is
632 663 placed at the next input prompt. Otherwise, the history is searched
633 664 for lines which contain that substring, and the most recent one is
634 665 placed at the next input prompt.
635 666 """
636 667 if not arg: # Last output
637 668 self.set_next_input(str(self.shell.user_ns["_"]))
638 669 return
639 670 # Get history range
640 671 histlines = self.history_manager.get_range_by_str(arg)
641 672 cmd = "\n".join(x[2] for x in histlines)
642 673 if cmd:
643 674 self.set_next_input(cmd.rstrip())
644 675 return
645 676
646 677 try: # Variable in user namespace
647 678 cmd = str(eval(arg, self.shell.user_ns))
648 679 except Exception: # Search for term in history
649 680 histlines = self.history_manager.search("*"+arg+"*")
650 681 for h in reversed([x[2] for x in histlines]):
651 682 if 'rep' in h:
652 683 continue
653 684 self.set_next_input(h.rstrip())
654 685 return
655 686 else:
656 687 self.set_next_input(cmd.rstrip())
657 688 print("Couldn't evaluate or find in history:", arg)
658 689
659 690 def magic_rerun(self, parameter_s=''):
660 691 """Re-run previous input
661 692
662 693 By default, you can specify ranges of input history to be repeated
663 694 (as with %history). With no arguments, it will repeat the last line.
664 695
665 696 Options:
666 697
667 698 -l <n> : Repeat the last n lines of input, not including the
668 699 current command.
669 700
670 701 -g foo : Repeat the most recent line which contains foo
671 702 """
672 703 opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
673 704 if "l" in opts: # Last n lines
674 705 n = int(opts['l'])
675 706 hist = self.history_manager.get_tail(n)
676 707 elif "g" in opts: # Search
677 708 p = "*"+opts['g']+"*"
678 709 hist = list(self.history_manager.search(p))
679 710 for l in reversed(hist):
680 711 if "rerun" not in l[2]:
681 712 hist = [l] # The last match which isn't a %rerun
682 713 break
683 714 else:
684 715 hist = [] # No matches except %rerun
685 716 elif args: # Specify history ranges
686 717 hist = self.history_manager.get_range_by_str(args)
687 718 else: # Last line
688 719 hist = self.history_manager.get_tail(1)
689 720 hist = [x[2] for x in hist]
690 721 if not hist:
691 722 print("No lines in history match specification")
692 723 return
693 724 histlines = "\n".join(hist)
694 725 print("=== Executing: ===")
695 726 print(histlines)
696 727 print("=== Output: ===")
697 728 self.run_cell("\n".join(hist), store_history=False)
698 729
699 730
700 731 def init_ipython(ip):
701 732 ip.define_magic("rep", magic_rep)
702 733 ip.define_magic("recall", magic_rep)
703 734 ip.define_magic("rerun", magic_rerun)
704 735 ip.define_magic("hist",magic_history) # Alternative name
705 736 ip.define_magic("history",magic_history)
706 737
707 738 # XXX - ipy_completers are in quarantine, need to be updated to new apis
708 739 #import ipy_completers
709 740 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
General Comments 0
You need to be logged in to leave comments. Login now