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