##// END OF EJS Templates
Initial fix for magic %rep function (was broken by new history system). Needs a bit of further thought.
Thomas Kluyver -
Show More
@@ -1,617 +1,610 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 execution count
51 51 output_hist = Instance(defaultdict)
52 52 # String holding the path to the history file
53 53 hist_file = Unicode()
54 54 # The SQLite database
55 55 db = Instance(sqlite3.Connection)
56 56 # The number of the current session in the history database
57 57 session_number = Int()
58 58 # Should we log output to the database? (default no)
59 59 db_log_output = Bool(False, config=True)
60 60 # Write to database every x commands (higher values save disk access & power)
61 61 # Values of 1 or less effectively disable caching.
62 62 db_cache_size = Int(0, config=True)
63 63 # The input and output caches
64 64 db_input_cache = List()
65 65 db_output_cache = List()
66 66
67 67 # Private interface
68 68 # Variables used to store the three last inputs from the user. On each new
69 69 # history update, we populate the user's namespace with these, shifted as
70 70 # necessary.
71 71 _i00, _i, _ii, _iii = '','','',''
72 72
73 73 # A set with all forms of the exit command, so that we don't store them in
74 74 # the history (it's annoying to rewind the first entry and land on an exit
75 75 # call).
76 76 _exit_commands = None
77 77
78 78 def __init__(self, shell, config=None):
79 79 """Create a new history manager associated with a shell instance.
80 80 """
81 81 # We need a pointer back to the shell for various tasks.
82 82 super(HistoryManager, self).__init__(shell=shell, config=config)
83 83
84 84 # list of visited directories
85 85 try:
86 86 self.dir_hist = [os.getcwd()]
87 87 except OSError:
88 88 self.dir_hist = []
89 89
90 90 # Now the history file
91 91 if shell.profile:
92 92 histfname = 'history-%s' % shell.profile
93 93 else:
94 94 histfname = 'history'
95 95 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.sqlite')
96 96 self.init_db()
97 97 self.new_session()
98 98
99 99 self._i00, self._i, self._ii, self._iii = '','','',''
100 100 self.output_hist = defaultdict(list)
101 101
102 102 self._exit_commands = set(['Quit', 'quit', 'Exit', 'exit', '%Quit',
103 103 '%quit', '%Exit', '%exit'])
104 104
105 105 def init_db(self):
106 106 """Connect to the database, and create tables if necessary."""
107 107 self.db = sqlite3.connect(self.hist_file)
108 108 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
109 109 primary key autoincrement, start timestamp,
110 110 end timestamp, num_cmds integer, remark text)""")
111 111 self.db.execute("""CREATE TABLE IF NOT EXISTS history
112 112 (session integer, line integer, source text, source_raw text,
113 113 PRIMARY KEY (session, line))""")
114 114 # Output history is optional, but ensure the table's there so it can be
115 115 # enabled later.
116 116 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
117 117 (session integer, line integer, output text,
118 118 PRIMARY KEY (session, line))""")
119 119 self.db.commit()
120 120
121 121 def new_session(self):
122 122 """Get a new session number."""
123 123 with self.db:
124 124 cur = self.db.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
125 125 NULL, "") """, (datetime.datetime.now(),))
126 126 self.session_number = cur.lastrowid
127 127
128 128 def end_session(self):
129 129 """Close the database session, filling in the end time and line count."""
130 130 self.writeout_cache()
131 131 with self.db:
132 132 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
133 133 session==?""", (datetime.datetime.now(),
134 134 len(self.input_hist_parsed)-1, self.session_number))
135 135 self.session_number = 0
136 136
137 137 def name_session(self, name):
138 138 """Give the current session a name in the history database."""
139 139 with self.db:
140 140 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
141 141 (name, self.session_number))
142 142
143 143 def reset(self, new_session=True):
144 144 """Clear the session history, releasing all object references, and
145 145 optionally open a new session."""
146 146 if self.session_number:
147 147 self.end_session()
148 148 self.input_hist_parsed[:] = [""]
149 149 self.input_hist_raw[:] = [""]
150 150 self.output_hist.clear()
151 151 # The directory history can't be completely empty
152 152 self.dir_hist[:] = [os.getcwd()]
153 153
154 154 if new_session:
155 155 self.new_session()
156 156
157 157 ## -------------------------------
158 158 ## Methods for retrieving history:
159 159 ## -------------------------------
160 160 def _get_hist_sql(self, sql, params, raw=True, output=False):
161 161 """Prepares and runs an SQL query for the history database.
162 162
163 163 Parameters
164 164 ----------
165 165 sql : str
166 166 Any filtering expressions to go after SELECT ... FROM ...
167 167 params : tuple
168 168 Parameters passed to the SQL query (to replace "?")
169 169 raw : bool
170 170 If True, get raw input.
171 171 output : bool
172 172 If True, include output where available.
173 173
174 174 Returns
175 175 -------
176 176 An iterator over 3-tuples: (session, line_number, command), or if output
177 177 is True, (session, line_number, (command, output)).
178 178 """
179 179 toget = 'source_raw' if raw else 'source'
180 180 sqlfrom = "history"
181 181 if output:
182 182 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
183 183 toget = "history.%s, output_history.output" % toget
184 184 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
185 185 (toget, sqlfrom) + sql, params)
186 186 if output: # Regroup into 3-tuples, and parse JSON
187 187 loads = lambda out: json.loads(out) if out else None
188 188 return ((ses, lin, (inp, loads(out))) \
189 189 for ses, lin, inp, out in cur)
190 190 return cur
191 191
192 192
193 193 def get_hist_tail(self, n=10, raw=True, output=False):
194 194 """Get the last n lines from the history database."""
195 195 self.writeout_cache()
196 196 cur = self._get_hist_sql("ORDER BY session DESC, line DESC LIMIT ?",
197 197 (n,), raw=raw, output=output)
198 198 return reversed(list(cur))
199 199
200 200 def get_hist_search(self, pattern="*", raw=True, output=False):
201 201 """Search the database using unix glob-style matching (wildcards * and
202 202 ?, escape using \).
203 203
204 204 Returns
205 205 -------
206 206 An iterator over tuples: (session, line_number, command)
207 207 """
208 208 tosearch = "source_raw" if raw else "source"
209 209 if output:
210 210 tosearch = "history." + tosearch
211 211 self.writeout_cache()
212 212 return self._get_hist_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
213 213 raw=raw, output=output)
214 214
215 215 def _get_hist_session(self, start=1, stop=None, raw=True, output=False):
216 216 """Get input and output history from the current session. Called by
217 217 get_history, and takes similar parameters."""
218 218 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
219 219
220 220 n = len(input_hist)
221 221 if start < 0:
222 222 start += n
223 223 if not stop:
224 224 stop = n
225 225 elif stop < 0:
226 226 stop += n
227 227
228 228 for i in range(start, stop):
229 229 if output:
230 230 output_item = [repr(x) for x in self.output_hist[i]]
231 231 line = (input_hist[i], output_item)
232 232 else:
233 233 line = input_hist[i]
234 234 yield (0, i, line)
235 235
236 236 def get_history(self, session=0, start=1, stop=None, raw=True,output=False):
237 237 """Retrieve input by session.
238 238
239 239 Parameters
240 240 ----------
241 241 session : int
242 242 Session number to retrieve. The current session is 0, and negative
243 243 numbers count back from current session, so -1 is previous session.
244 244 start : int
245 245 First line to retrieve.
246 246 stop : int
247 247 End of line range (excluded from output itself). If None, retrieve
248 248 to the end of the session.
249 249 raw : bool
250 250 If True, return untranslated input
251 251 output : bool
252 252 If True, attempt to include output. This will be 'real' Python
253 253 objects for the current session, or text reprs from previous
254 254 sessions if db_log_output was enabled at the time. Where no output
255 255 is found, None is used.
256 256
257 257 Returns
258 258 -------
259 259 An iterator over the desired lines. Each line is a 3-tuple, either
260 260 (session, line, input) if output is False, or
261 261 (session, line, (input, output)) if output is True.
262 262 """
263 263 if session == 0 or session==self.session_number: # Current session
264 264 return self._get_hist_session(start, stop, raw, output)
265 265 if session < 0:
266 266 session += self.session_number
267 267
268 268 if stop:
269 269 lineclause = "line >= ? AND line < ?"
270 270 params = (session, start, stop)
271 271 else:
272 272 lineclause = "line>=?"
273 273 params = (session, start)
274 274
275 275 return self._get_hist_sql("WHERE session==? AND %s""" % lineclause,
276 276 params, raw=raw, output=output)
277 277
278 278 def get_hist_from_rangestr(self, rangestr, raw=True, output=False):
279 279 """Get lines of history from a string of ranges, as used by magic
280 280 commands %hist, %save, %macro, etc."""
281 281 for sess, s, e in extract_hist_ranges(rangestr):
282 282 for line in self.get_history(sess, s, e, raw=raw, output=output):
283 283 yield line
284 284
285 285 ## ----------------------------
286 286 ## Methods for storing history:
287 287 ## ----------------------------
288 288 def store_inputs(self, line_num, source, source_raw=None):
289 289 """Store source and raw input in history and create input cache
290 290 variables _i*.
291 291
292 292 Parameters
293 293 ----------
294 294 line_num : int
295 295 The prompt number of this input.
296 296
297 297 source : str
298 298 Python input.
299 299
300 300 source_raw : str, optional
301 301 If given, this is the raw input without any IPython transformations
302 302 applied to it. If not given, ``source`` is used.
303 303 """
304 304 if source_raw is None:
305 305 source_raw = source
306 306
307 307 # do not store exit/quit commands
308 308 if source_raw.strip() in self._exit_commands:
309 309 return
310 310
311 311 self.input_hist_parsed.append(source.rstrip())
312 312 self.input_hist_raw.append(source_raw.rstrip())
313 313
314 314 self.db_input_cache.append((self.session_number, line_num,
315 315 source, source_raw))
316 316 # Trigger to flush cache and write to DB.
317 317 if len(self.db_input_cache) >= self.db_cache_size:
318 318 self.writeout_cache()
319 319
320 320 # update the auto _i variables
321 321 self._iii = self._ii
322 322 self._ii = self._i
323 323 self._i = self._i00
324 324 self._i00 = source_raw
325 325
326 326 # hackish access to user namespace to create _i1,_i2... dynamically
327 327 new_i = '_i%s' % line_num
328 328 to_main = {'_i': self._i,
329 329 '_ii': self._ii,
330 330 '_iii': self._iii,
331 331 new_i : self._i00 }
332 332 self.shell.user_ns.update(to_main)
333 333
334 334 def store_output(self, line_num):
335 335 if (not self.db_log_output) or not self.output_hist[line_num]:
336 336 return
337 337 output = json.dumps([repr(x) for x in self.output_hist[line_num]])
338 338 db_row = (self.session_number, line_num, output)
339 339 if self.db_cache_size > 1:
340 340 self.db_output_cache.append(db_row)
341 341 else:
342 342 with self.db:
343 343 self.db.execute("INSERT INTO output_history VALUES (?,?,?)", db_row)
344 344
345 345 def writeout_cache(self):
346 346 #print(self.db_input_cache)
347 347 with self.db:
348 348 self.db.executemany("INSERT INTO history VALUES (?, ?, ?, ?)",
349 349 self.db_input_cache)
350 350 self.db.executemany("INSERT INTO output_history VALUES (?, ?, ?)",
351 351 self.db_output_cache)
352 352 self.db_input_cache = []
353 353 self.db_output_cache = []
354 354
355 355
356 356 # To match, e.g. ~5/8-~2/3
357 357 range_re = re.compile(r"""
358 358 ((?P<startsess>~?\d+)/)?
359 359 (?P<start>\d+) # Only the start line num is compulsory
360 360 ((?P<sep>[\-:])
361 361 ((?P<endsess>~?\d+)/)?
362 362 (?P<end>\d+))?
363 363 """, re.VERBOSE)
364 364
365 365 def extract_hist_ranges(ranges_str):
366 366 """Turn a string of history ranges into 3-tuples of (session, start, stop).
367 367
368 368 Examples
369 369 --------
370 370 list(extract_input_ranges("~8/5-~7/4 2"))
371 371 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
372 372 """
373 373 for range_str in ranges_str.split():
374 374 rmatch = range_re.match(range_str)
375 if not rmatch:
376 continue
375 377 start = int(rmatch.group("start"))
376 378 end = rmatch.group("end")
377 379 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
378 380 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
379 381 end += 1
380 382 startsess = rmatch.group("startsess") or "0"
381 383 endsess = rmatch.group("endsess") or startsess
382 384 startsess = int(startsess.replace("~","-"))
383 385 endsess = int(endsess.replace("~","-"))
384 386 assert endsess >= startsess
385 387
386 388 if endsess == startsess:
387 389 yield (startsess, start, end)
388 390 continue
389 391 # Multiple sessions in one range:
390 392 yield (startsess, start, None)
391 393 for sess in range(startsess+1, endsess):
392 394 yield (sess, 1, None)
393 395 yield (endsess, 1, end)
394 396
395 397 def _format_lineno(session, line):
396 398 """Helper function to format line numbers properly."""
397 399 if session == 0:
398 400 return str(line)
399 401 return "%s#%s" % (session, line)
400 402
401 403 @testdec.skip_doctest
402 404 def magic_history(self, parameter_s = ''):
403 405 """Print input history (_i<n> variables), with most recent last.
404 406
405 407 %history -> print at most 40 inputs (some may be multi-line)\\
406 408 %history n -> print at most n inputs\\
407 409 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
408 410
409 411 By default, input history is printed without line numbers so it can be
410 412 directly pasted into an editor.
411 413
412 414 With -n, each input's number <n> is shown, and is accessible as the
413 415 automatically generated variable _i<n> as well as In[<n>]. Multi-line
414 416 statements are printed starting at a new line for easy copy/paste.
415 417
416 418 Options:
417 419
418 420 -n: print line numbers for each input.
419 421 This feature is only available if numbered prompts are in use.
420 422
421 423 -o: also print outputs for each input.
422 424
423 425 -p: print classic '>>>' python prompts before each input. This is useful
424 426 for making documentation, and in conjunction with -o, for producing
425 427 doctest-ready output.
426 428
427 429 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
428 430
429 431 -t: print the 'translated' history, as IPython understands it. IPython
430 432 filters your input and converts it all into valid Python source before
431 433 executing it (things like magics or aliases are turned into function
432 434 calls, for example). With this option, you'll see the native history
433 435 instead of the user-entered version: '%cd /' will be seen as
434 436 'get_ipython().magic("%cd /")' instead of '%cd /'.
435 437
436 438 -g: treat the arg as a pattern to grep for in (full) history.
437 439 This includes the saved history (almost all commands ever written).
438 440 Use '%hist -g' to show full saved history (may be very long).
439 441
440 442 -l: get the last n lines from all sessions. Specify n as a single arg, or
441 443 the default is the last 10 lines.
442 444
443 445 -f FILENAME: instead of printing the output to the screen, redirect it to
444 446 the given file. The file is always overwritten, though IPython asks for
445 447 confirmation first if it already exists.
446 448
447 449 Examples
448 450 --------
449 451 ::
450 452
451 453 In [6]: %hist -n 4 6
452 454 4:a = 12
453 455 5:print a**2
454 456
455 457 """
456 458
457 459 if not self.shell.displayhook.do_full_cache:
458 460 print('This feature is only available if numbered prompts are in use.')
459 461 return
460 462 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
461 463
462 464 # For brevity
463 465 history_manager = self.shell.history_manager
464 466
465 467 def _format_lineno(session, line):
466 468 """Helper function to format line numbers properly."""
467 469 if session in (0, history_manager.session_number):
468 470 return str(line)
469 471 return "%s/%s" % (session, line)
470 472
471 473 # Check if output to specific file was requested.
472 474 try:
473 475 outfname = opts['f']
474 476 except KeyError:
475 477 outfile = IPython.utils.io.Term.cout # default
476 478 # We don't want to close stdout at the end!
477 479 close_at_end = False
478 480 else:
479 481 if os.path.exists(outfname):
480 482 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
481 483 print('Aborting.')
482 484 return
483 485
484 486 outfile = open(outfname,'w')
485 487 close_at_end = True
486 488
487 489 print_nums = 'n' in opts
488 490 get_output = 'o' in opts
489 491 pyprompts = 'p' in opts
490 492 # Raw history is the default
491 493 raw = not('t' in opts)
492 494
493 495 default_length = 40
494 496 pattern = None
495 497
496 498 if 'g' in opts: # Glob search
497 499 pattern = "*" + args + "*" if args else "*"
498 500 hist = history_manager.get_hist_search(pattern, raw=raw,
499 501 output=get_output)
500 502 elif 'l' in opts: # Get 'tail'
501 503 try:
502 504 n = int(args)
503 505 except ValueError, IndexError:
504 506 n = 10
505 507 hist = history_manager.get_hist_tail(n, raw=raw, output=get_output)
506 508 else:
507 509 if args: # Get history by ranges
508 510 hist = history_manager.get_hist_from_rangestr(args, raw, get_output)
509 511 else: # Just get history for the current session
510 512 hist = history_manager.get_history(raw=raw, output=get_output)
511 513
512 514 # We could be displaying the entire history, so let's not try to pull it
513 515 # into a list in memory. Anything that needs more space will just misalign.
514 516 width = 4
515 517
516 518 for session, lineno, inline in hist:
517 519 # Print user history with tabs expanded to 4 spaces. The GUI clients
518 520 # use hard tabs for easier usability in auto-indented code, but we want
519 521 # to produce PEP-8 compliant history for safe pasting into an editor.
520 522 if get_output:
521 523 inline, output = inline
522 524 inline = inline.expandtabs(4).rstrip()
523 525
524 526 multiline = "\n" in inline
525 527 line_sep = '\n' if multiline else ' '
526 528 if print_nums:
527 529 print('%s:%s' % (_format_lineno(session, lineno).rjust(width),
528 530 line_sep), file=outfile, end='')
529 531 if pyprompts:
530 532 print(">>> ", end="", file=outfile)
531 533 if multiline:
532 534 inline = "\n... ".join(inline.splitlines()) + "\n..."
533 535 print(inline, file=outfile)
534 536 if get_output and output:
535 537 print("\n".join(output), file=outfile)
536 538
537 539 if close_at_end:
538 540 outfile.close()
539 541
540 # %hist is an alternative name
541 magic_hist = magic_history
542
543 542
544 def rep_f(self, arg):
543 def magic_rep(self, arg):
545 544 r""" Repeat a command, or get command to input line for editing
546 545
547 546 - %rep (no arguments):
548 547
549 548 Place a string version of last computation result (stored in the special '_'
550 549 variable) to the next input prompt. Allows you to create elaborate command
551 550 lines without using copy-paste::
552 551
553 552 $ l = ["hei", "vaan"]
554 553 $ "".join(l)
555 554 ==> heivaan
556 555 $ %rep
557 556 $ heivaan_ <== cursor blinking
558 557
559 558 %rep 45
560 559
561 560 Place history line 45 to next input prompt. Use %hist to find out the
562 561 number.
563 562
564 563 %rep 1-4 6-7 3
565 564
566 565 Repeat the specified lines immediately. Input slice syntax is the same as
567 566 in %macro and %save.
568 567
569 568 %rep foo
570 569
571 570 Place the most recent line that has the substring "foo" to next input.
572 571 (e.g. 'svn ci -m foobar').
573 572 """
574 573
575 574 opts,args = self.parse_options(arg,'',mode='list')
576 if not args:
575 if not args: # Last output
577 576 self.set_next_input(str(self.shell.user_ns["_"]))
578 577 return
579 578
580 if len(args) == 1 and not '-' in args[0]:
581 arg = args[0]
582 if len(arg) > 1 and arg.startswith('0'):
583 # get from shadow hist
584 num = int(arg[1:])
585 line = self.shell.shadowhist.get(num)
586 self.set_next_input(str(line))
587 return
588 try:
589 num = int(args[0])
590 self.set_next_input(str(self.shell.input_hist_raw[num]).rstrip())
591 return
592 except ValueError:
593 pass
594
595 for h in reversed(self.shell.input_hist_raw):
579 arg = " ".join(args)
580 histlines = self.history_manager.get_hist_from_rangestr(arg, raw=False)
581 histlines = [x[2] for x in histlines]
582
583 if len(histlines) > 1: # Execute immediately
584 histlines = "\n".join(histlines)
585 print("=== Executing: ===")
586 print(histlines)
587 print("=== Output: ===")
588 self.run_source(histlines, symbol="exec")
589
590 elif len(histlines) == 1: # Editable input
591 self.set_next_input(histlines[0].rstrip())
592
593 else: # Search for term - editable input
594 histlines = self.history_manager.get_hist_search("*"+arg+"*")
595 for h in reversed([x[2] for x in histlines]):
596 596 if 'rep' in h:
597 597 continue
598 if fnmatch.fnmatch(h,'*' + arg + '*'):
599 self.set_next_input(str(h).rstrip())
600 return
601
602 try:
603 lines = self.extract_input_slices(args, True)
604 print("lines", lines)
605 self.run_cell(lines)
606 except ValueError:
607 print("Not found in recent history:", args)
598 self.set_next_input(h.rstrip())
599 return
600 print("Not found in history:", arg)
608 601
609 602
610 603 def init_ipython(ip):
611 ip.define_magic("rep",rep_f)
612 ip.define_magic("hist",magic_hist)
604 ip.define_magic("rep", magic_rep)
605 ip.define_magic("hist",magic_history) # Alternative name
613 606 ip.define_magic("history",magic_history)
614 607
615 608 # XXX - ipy_completers are in quarantine, need to be updated to new apis
616 609 #import ipy_completers
617 610 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
General Comments 0
You need to be logged in to leave comments. Login now