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