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