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